Skip to content

Sourcery

Robin edited this page Jul 19, 2024 · 4 revisions

All packages use Sourcery to generate some mock and tests scenarios.

AutoMockable.generated

Use to generate generated mock for protocols.

To add mock for your protocol, you should add the // sourcery: AutoMockable annotation before the definition.

// sourcery: AutoMockable
protocol MyProtocol {
}

Reset

It also contains a ResetGeneratedMock used by procotols which contains functions to reset all mock properties.

// sourcery: AutoMockable
protocol MyProtocolGeneratedMock {

    func reset() {
        XXXCallsCount = 0
        XXXReceivedArguments = nil
        XXXReceivedInvocations = []
    }
}

Associated type

The mock can genarated subclasses for each associatedtypes contained into your protocol.

Note: the associatedtype MUST be an Equatable. If not, please don't use AutoMockable.

Example of the definition:

// sourcery: AutoMockable
protocol MyProtocol {
    associatedtype Other: Equatable

    // ...
}

The generated mock:

final class MyProtocolGeneratedMock: MyProtocolGeneratedMock {

    // MARK: - Type Alias

    typealias Other = OtherMock

    // MARK: - Associated Type

    struct OtherMock: Equatable {
        let id = UUID().uuidString
    }

AutoMockTest.generated

Use to test all functions in your protocol:

  • The number of call
  • The given parameter(s)
  • The return value (if the number of call is greater than 0)

To add generated test for your class, you should add the // sourcery: AutoMockTest annotation before the definition.

// sourcery: AutoMockTest
protocol MyProtocol {
}

Type

If one of the parameters or the return value are not Equatable, you should add an Identical annotation.

Example when the first parameter is Equatable and the second parameter and the return are not Equatable:

// sourcery: AutoMockTest
protocol MyProtocol {
    // sourcery: second = "Identical", return = "Identical"
    func execute(first: Bool,
                 second: Second) -> Return
}

Why ?!

Because it's not possible to test with XCTAssertEqual a non Equatable property ! So, in this case, the only way to test property is to using the XCTAssertIdentical.

Test

If we have this mock:

// sourcery: AutoMockTest
protocol MyProtocol {
    func execute(first: String,
                 second: Bool) -> Bool
}

The syntax to test your mock is:

// GIVEN / WHEN
let mock = MyProtocolGeneratedMock()

// THEN

// Test number of calls, parameters and return value
MyProtocolMockTest.XCTAssert(
    mock,
    expectedNumberOfCalls: 1,
    givenFirst: "First"
    givenSecond: true
    expectedReturnValue: false
)

// OR

// Test only the number of calls
MyProtocolMockTest.XCTCallsCount(
    mock,
    executeWithFirstAndSecondNumberOfCalls: 1
)

AutoPublisherTest.generated

Use to test all @Published variables in your class:

  • The number of sinks
  • The sink value (if the number of sinks is greater than 0)

To add generated test for your class, you should add the // sourcery: AutoPublisherTest annotation before the definition.

// sourcery: AutoPublisherTest
class MyClass {
}

Generic type

If your class required an generic type, you must add an annotation with the mock of the generic type.

Note: The key must be in angle brackets (< and >).

// sourcery: AutoMockable
protocol MyProtocol {
}

// sourcery: AutoPublisherTest
// sourcery: <P> = "MyProtocolGeneratedMock"
class MyClass<P: MyProtocol> {
    @Published private (set) var myProperty: P?
}

Type

If your @Published value is not an Equatable, you should add an Identical annotation.

Example:

struct NotEquatable {}

// sourcery: AutoPublisherTest
// sourcery: myProperty = "Identical"
class MyClass {
    @Published private (set) var myProperty: NotEquatable?
}

Why ?!

Because it's not possible to test with XCTAssertEqual a non Equatable property ! So, in this case, the only way to test property is to using the XCTAssertIdentical.

Test

If we have this class:

// sourcery: AutoPublisherTest
class MyClass {
    @Published private (set) var myValue: Int?
}

The syntax to test your mock is:

// GIVEN / WHEN
let myClass = MyClass()

var subscriptions = Set<AnyCancellable>()

let myValuePublisherMock = .init(publisher: myClass.$myValue)
myValuePublisherMock.loadTesting(on: &subscriptions)

// THEN

// Test number of sinks and sink value
MyClassPublisherTest.XCTAssert(
    myValue: myValuePublisherMock,
    expectedNumberOfSinks: 1,
    expectedValue: 11
)

// OR

// Test only the number of sinks
MyClassPublisherTest.XCTSinksCount(
    myValue: myValuePublisherMock,
    expectedNumberOfSinks: 1
)

AutoViewModelStub.generated

Use to create a stub for ViewModel.

The stub can contains dependencies (UseCases, ...) and PublisherMock if the ViewModel has this type of data.

To add generated test for your ViewModel, you should add the // sourcery: AutoViewModelStub annotation before the definition.

// sourcery: AutoViewModelStub
class MyViewModel {
}

Generic type

If your ViewModel required an generic type, you must add an annotation with the mock of the generic type.

Note: The key must be in angle brackets (< and >).

// sourcery: AutoMockable
protocol MyProtocol {
}

// sourcery: AutoViewModelStub
// sourcery: <P> = "MyProtocolGeneratedMock"
class MyViewModel<P: MyProtocol> {
    private let useCase: P
}

Subscription

If your ViewModel contains at least one @Published property, to load the subscription of the Publisher, you need to call the subscribePublishers with an Set stored on your XCTest:

var subscriptions = Set<AnyCancellable>()

func test_XXX() {
    let stub = MyViewModelStub()
    stub.subscribePublishers(on: &self.subscriptions)

    // ...
}

Reset

If you want to reset all mock data for dependencies (number of calls, arguments, return value, ...) or @Published (number of sinks, sink value), you need to call the resetMockedData function:

var subscriptions = Set<AnyCancellable>()

func test_XXX() {
    let stub = MyViewModelStub()
    stub.resetMockedData()

    // ...
}

Test

If we have this UseCase:

// sourcery: AutoMockable
// sourcery: AutoMockTest
protocol MyUseCase {
    func execute(first: Int) -> Bool
}

And this ViewModel:

// sourcery: AutoViewModelStub
// sourcery: AutoPublisherTest
class MyViewModel {
    @Published private (set) var myValue: Int?

    let myUseCase: MyUseCase
}

The final implementation for the stub is:

final class Stub: MyViewModelStub {
    
    init() {
        let myUseCaseMock = MyUseCaseGeneratedMock()
        myUseCaseMock.executeWithFirstReturnValue = true

        let viewModel = MyViewModel(
            myUseCase: myUseCaseMock
        )

        super.init(
            viewModel: viewModel,
            myUseCaseMock: myUseCaseMock
        )
    }
}

The syntax to test your mock is:

// GIVEN
var subscriptions = Set<AnyCancellable>()

let stub = Stub()
stub.subscribePublishers(on: &self.subscriptions)
stub.resetMockedData() // You can reset mock after the init of the stub if you want.

// WHEN

// ...

// THEN

// **
// Publisher
MyViewModelPublisherTest.XCTAssert(
    myValue: stub.myValuePublisherMock,
    expectedNumberOfSinks: 1,
    expectedValue: 22
)

// OR

MyViewModelPublisherTest.XCTSinksCount(
    myValue: stub.myValuePublisherMock,
    expectedNumberOfSinks: 1
)
// **

// **
// UseCase
MyUseCaseMockTest.XCTAssert(
    stub.myUseCaseMock,
    expectedNumberOfCalls: 1,
    expectedReturnValue: true
)

// OR

MyUseCaseMockTest.XCTCallsCount(
    stub.myUseCaseMock,
    executeWithFirstNumberOfCalls: 1
)
// **