XCTestCase to check if a method is called within a Struct

pls picture pls · May 19, 2016 · Viewed 7.8k times · Source

I am trying to test a piece of code where I check to see if an account has already created a secret key and stored it in the keychain. If not it calls a method that starts the oauth process.

My first thought was override the method which I would like to call if the user hasn't got a secret key. However I am using a struct and thus can't inherit and override the method.

If I was using a class I would so something like:

func testInitiateOauthCalledIfSecretKeyNotFound() {

    class MockKeychainAccess: KeychainAccess {
       var initiateAuthorizationWasCalled: Bool = false
        override initiateAuthorization() {
             initiateAuthorizationWasCalled = true
        }

    let keychainAccess = MockKeychainAccess()
    keychainAccess.authorizeWithGoogle()
    XCTAssertTrue(initiateAuthorizationWasCalled)
}

I haven't tested this code so not sure if it compiles. However logically it seems it would handle the case I am after. If within the authorizeWithGoogle method we call initiateAuthorization() then I would know that that has occurred. However one can not do this when they are using a struct as we can't inherit from a struct.

Please note: I am new to TDD so maybe I am thinking about this the wrong way. Other suggestions are welcome. However I do not want to convert from a struct to a class just to write a test. I am using structs as I am trying to be more swift like.

Does anyone know a way I could test whether a function is called within a struct?

==========

Edit:

In response to dasdom answer I am adding an example of the general approach I am trying to achieve:

override func viewDidLoad() {
    setupView()

    let api = DataApi()
    getData(api)
}

func setupView() {
    tableView.dataSource = tableViewDataSource
}

func getData(api: DataApi) {

    api.getApplicationData( { (objects) in
        if let applications = objects as? [Application] {
            self.tableViewDataSource.setApplicationItems(applications)
            self.tableView.reloadData()
        }
        else {
            // Display error
        }
    })

}

So I would like to inject the MockDataApi so that it can return what I want as the method takes in a type of DataApi. However I am not sure how I should create this MockDataApi struct and pass it into this method.

Could someone help in regards to how to build this mock object for this purpose and use it? I realise it's with protocols but struggling to piece it together.

Answer

dasdom picture dasdom · May 21, 2016

Use a protocol. Make your class/struct and your test mock conform to the protocol. Inject the dependency and assert that the expected method gets called in your mock.

Edit: Example

protocol DataApiProtocol {
    func getApplicationData(block: [AnyObject] -> Void)
}

// Production code
struct DataApi: DataApiProtocol {
    func getApplicationData(block: [AnyObject] -> Void) {
        // do stuff
    }

    // more properties and methods
}

// Mock code
struct MockDataApi: DataApiProtocol {
    var getApplicationDataGotCalled = false
    func getApplicationData(block: [AnyObject] -> Void) {
        getApplicationDataGotCalled = true
    }
}

// Test code
func testGetData_CallsGetApplicationData() {
    let sut = MyAwesomeClass()
    let mockDataApi = MockDataApi()        

    sut.getData(mockDataApi)

    XCTAssertTrue(mockDataApi.getApplicationDataGotCalled)
}

I hope this helps.