In the world of software development, writing unit tests is essential for ensuring the reliability and maintainability of code. However, creating effective unit tests can sometimes be challenging, especially when dealing with dependencies such as network requests, databases, or external services. This is where mocking frameworks come into play.

SwiftyMocky is a powerful mocking framework for Swift that simplifies the process of mocking objects and functions during unit testing. In this article, we will explore how to use SwiftyMocky to streamline your unit testing workflow and write more robust tests.

Getting Started with SwiftyMocky

To install SwiftyMocky using Fastlane, you can create a custom lane in your Fastfile. But before we should install Fastlane using gems. You need just update your GemFile like this

gem 'fastlane', '~> 2.212.1'

ℹ️ if you didn’t have yet a GemFile you need only install a correct version of ruby for example 3.3.0.

bundle install

Then, run this command if needed to install SwiftyMocky-CLI:

bundle exec fastlane installSwiftyMocky

Fastlane will generate this lanes on your fastFile file.

ensure_bundle_exec
default_platform(:ios)

###############################
## Life Cycle hooker
###############################

error do |lane, exception, options|
 # _notification_error(lane, exception, options)
end

###############################
## Public Lanes
###############################

lane :installSwiftyMocky do
  sh("bundle exec brew install mint")
  sh("bundle exec mint install MakeAWishFoundation/SwiftyMocky")
end

lane :uninstallSwiftyMocky do
  sh("bundle exec mint uninstall MakeAWishFoundation/SwiftyMocky")
  sh("bundle exec brew uninstall mint")
end

lane :generateMocks do
  sh("bundle exec mint run MakeAWishFoundation/SwiftyMocky generate")
end

We will use later this lane :generateMocks to generate our mocks. But before that we need now to configure how swiftymocky will generate our mocks.

SwiftyMocky falls into second category (originally proposed by Przemysław Wośko). Whole concept is based on Sourcery(written by Krzysztof Zabłocki) and utilizes meta-programming concept.

We need to create our Mockfile with this content for example

sourceryCommand: mint run krzysztofzablocki/Sourcery@2.1.3 sourcery
sourceryTemplate: null

SwiftyMockyProject:
  sources:
    include:
    - ./SwiftyMockyProject
  output: 
    ./SwiftyMockyProjectTests/Common/Mocks/Mock.generated.swift
  targets:
  - SwiftyMockyProjectTests
  testable:
  - SwiftyMockyProject
  import:
  - Foundation
  - RxCocoa
  - RxSwift
  - UIKit

The provided configuration appears to be related to Sourcery and SwiftyMocky within a project setup. Let’s break down the configuration:

  1. Sourcery Command: It specifies the command to run Sourcery. mint run krzysztofzablocki/Sourcery@2.1.3 sourcery suggests the usage of Mint to run Sourcery version 2.1.3.
  2. Sourcery Template: This field is set to null, indicating that no specific template is specified. Sourcery usually generates code based on templates.
  3. SwiftyMockyProject:
    • Sources: It includes the directory ./SwiftyMockyProject for sourcing files.
    • Output: Generated mock files will be placed at ./SwiftyMockyProjectTests/Common/Mocks/Mock.generated.swift.
    • Targets: The tests target SwiftyMockyProjectTests is specified.
    • Testable: The main project SwiftyMockyProject is marked as testable, indicating that it’s accessible for testing.
    • Import: Libraries and frameworks needed for generating mock files are specified, including Foundation, RxCocoa, RxSwift, and UIKit.

But you should have mint installed on your machine using this command

bundle exec brew install mint

ℹ️ We are using Mint to run Sourcery at a specific version (2.1.3) due to limitations or version differences between the default version of Sourcery provided by Fastlane (1.8) and the one required by your project.

Using Mint to execute a specific version of Sourcery is a common workaround to bypass version differences and ensure the tool is compatible with the specific needs of your project.

Inside our code

To generate mocks, we need make some staff on our code specially using protocol and annotate with a sourcery annotation like this for example:

import Foundation
import RxSwift

//sourcery: AutoMockable
protocol PhotosRepository {

    func fetchPhotos() -> Observable<[Photo]>

}

import Foundation
import RxSwift
import UIKit

//sourcery: AutoMockable
protocol ImageDownloader {

    func image(for urlString: String) -> Observable<UIImage?>
}

Now we need only to run our fastlane lane using this command

bundle exec fastlane generateMocks

Sourcery will generate automatically a new file which is the Mock.generated.swift and place it in this path ./SwiftyMockyProjectTests/Common/Mocks/Mock.generated.swift

Now write our tests!

import RxSwift

@testable import SwiftyMockyProject

final class PhotosViewModelTests: XCTestCase {
private var sut: PhotosViewModel!
    
    private let imageDownloader = ImageDownloaderMock()
    
    private lazy var photoRepository: PhotosRepositoryMock = {
        let photoRepository = PhotosRepositoryMock()
        
        photoRepository.given(.fetchPhotos(willReturn: Observable<[Photo]>.mockInject()))
        
        return photoRepository
    }()
}

We used our imageDownloader and also our repository as dependencies in our photosViewModel which is our system under test (SUT). To make tests on our view model, we need to inject dependencies as mocks.

We check the invocation of our method fetchPhotos using the photoRepositoryMock object by given a fake observable value. We created an extension static method on Observable type to do this. We called it mockInject().

import Foundation
import RxCocoa
import RxSwift

private var xoSubjectAssociationKey: UInt8 = 0
private var xoSpyAssociationKey: UInt8 = 1

extension ObservableType {
    
    static func mockInject() -> Observable<Element> {
        let subject = PublishSubject<Element>()
        let observable = subject.asObservable()
        observable.storeSubject(newValue: subject)
        return observable
    }
    
    func storeSubject(newValue: PublishSubject<Element>) {
        objc_setAssociatedObject(
            self,
            &xoSubjectAssociationKey,
            newValue,
            objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
        )
    }
}

You can easily make stubbing also by giving non observable value like this for example

// Arrange

        let basketDTOMock = BasketDTOProtocolMock()
        
        basketDTOMock.given(.id(getter: Constants.basketId))
      
        basketServiceManagerMock.given(.checkProductsInventory(willReturn: .just(basketDTOMock)))

Summary

SwiftyMocky serves as a powerful mocking framework for Swift, streamlining the process of creating mock objects for unit testing. With SwiftyMocky:

  1. Efficient Mock Creation: SwiftyMocky simplifies the creation of mock objects, allowing developers to quickly generate mocks for dependencies such as network requests, databases, or external services.
  2. Fluent API: It provides a fluent API for defining mock behaviors and expectations, making it easy to set up and verify interactions with mocked objects.
  3. Enhanced Testing Workflow: By integrating SwiftyMocky into your testing workflow, you can write more robust tests with less effort, improving the reliability and maintainability of your codebase.
  4. Swift Package Manager Integration: SwiftyMocky seamlessly integrates with Swift Package Manager, enabling easy integration into Swift projects.
  5. Support for Fastlane Integration: SwiftyMocky’s flexibility allows for integration with tools like Fastlane, enabling efficient automation of the mocking process within your project setup.

References

https://blog.girappe.com/?swiftymocky/

https://github.com/MakeAWishFoundation/SwiftyMocky

https://github.com/krzysztofzablocki/Sourcery

Leave A Comment

Solve : *
36 ⁄ 18 =