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.

Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75
  • possible duplicate of [Mocking in Swift](http://stackoverflow.com/questions/24174130/mocking-in-swift) – ColinE Sep 26 '14 at 05:39
  • 1
    The difference from my question to that one is that I want to expect a method call and not just mock a method. – Rodrigo Ruiz Oct 01 '14 at 22:18

1 Answers1

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() {

    }

}
e1985
  • 6,239
  • 1
  • 24
  • 39
  • 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). – Rodrigo Ruiz Oct 01 '14 at 21:53
  • Also I can't override a method inside the test like you did. Same error, "SourceKitService Crashed". – Rodrigo Ruiz Oct 01 '14 at 21:54
  • 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. – e1985 Oct 01 '14 at 23:25
  • 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 http://stackoverflow.com/questions/24006206/sourcekitservice-terminated – Rodrigo Ruiz Oct 02 '14 at 04:33
  • And what else would you do to test a method call then without creating a variable for every different mock? – Rodrigo Ruiz Oct 02 '14 at 04:34
  • And if you are testing a method that receives a block, doesn't that count as async? – Rodrigo Ruiz Oct 02 '14 at 04:45
  • 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. – e1985 Oct 02 '14 at 08:41
  • Regarding sync/async, receiving a block does not imply necessary that your test is asynchronous, it depends on the functionality you want to test. – e1985 Oct 02 '14 at 08:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62329/discussion-between-e1985-and-rodrigo-ruiz). – e1985 Oct 02 '14 at 11:17