XCTest expect method call with swift
Asked Answered
T

1

8

How to write a test that expects a method call using swift and XCTest?

I could use OCMock, but they don't officially support swift, so not much of an option.

Telegraphic answered 26/9, 2014 at 5:21 Comment(2)
possible duplicate of Mocking in SwiftGravy
The difference from my question to that one is that I want to expect a method call and not just mock a method.Telegraphic
L
4

As you said OCMock does not support Swift(nor OCMockito), so for now the only way I see is to create a hand rolled mock. In swift this is a little bit less painful since you can create inner classes within a method, but still is not as handy as a mocking framework.

Here you have an example. The code is self explanatory, the only thing I had to do(see EDIT 1 below) to make it work is to declare the classes and methods I want to use from the test as public(seems that the test classes do not belong to the same application's code module - will try to find a solution to this).

EDIT 1 2016/4/27: Declaring the classes you want test as public is not necessary anymore since you can use the "@testable import ModuleName" feature.

The test:

import XCTest
import SwiftMockingPoC

class MyClassTests: XCTestCase {

    func test__myMethod() {
        // prepare
        class MyServiceMock : MyService {

            var doSomethingWasCalled = false

            override func doSomething(){
                doSomethingWasCalled = true
            }

        }
        let myServiceMock = MyServiceMock()

        let sut = MyClass(myService: myServiceMock)

        // test
        sut.myMethod()

        // verify
        XCTAssertTrue(myServiceMock.doSomethingWasCalled)
    }

}

MyClass.swift

public class MyClass {

    let myService: MyService

    public init(myService: MyService) {
        self.myService = myService
    }

    public func myMethod() {
        myService.doSomething()
    }

}

MyService.swift

public class MyService {

    public init() {

    }

    public func doSomething() {

    }

}
Litch answered 1/10, 2014 at 15:28 Comment(9)
I wouldn't like to create a variable just for "doSomethingWasCalled" inside the mock class and then have an assert for every method I expect to be called. I wanted something more like declaring a variable outside the mock class with expectationWithDescription, fulfill its expectation in the mock class method and then call waitForExpectationsWithTimeout in the test. But that doesn't work, the test doesn't compile (or gives me the "SourceKitService Crashed" error, don't really remember, and can't test right now because my code is not compiling because of something else I'm implementing).Telegraphic
Also I can't override a method inside the test like you did. Same error, "SourceKitService Crashed".Telegraphic
AFAIK XCTestExpectation is ment for asynchronous testing, I see no reason to use it unless you want so. About overriding that method, make sure you are declaring public the methods and classes you want to use in your test - the code of my answer works for me in Xcode.Litch
I'm still trying to resolve a bug with importing the FacebookSDK and testing, so I'll test your code again later, but I'm pretty sure I was getting "SourceKitService Crashed", similar to this problem #24006706Telegraphic
And what else would you do to test a method call then without creating a variable for every different mock?Telegraphic
And if you are testing a method that receives a block, doesn't that count as async?Telegraphic
I also got that error a bunch on times when I was writing that code, if I remember well it was when I was declaring doSomethingWasCalled as a local variable in test__myMethod instead of as a property in MyServiceMock. Also, don't forget to declare your classes and methods as public. About "creating a variable for every different mock"(not sure what you mean here), this solution implies creating a variable for every expectation. In case you want to use the same mock class in more than one method you could define it within your test class instead of within the test method.Litch
Regarding sync/async, receiving a block does not imply necessary that your test is asynchronous, it depends on the functionality you want to test.Litch
Let us continue this discussion in chat.Litch

© 2022 - 2024 — McMap. All rights reserved.