Search code examples
iosswiftstructxctestxctestcase

XCTestCase to check if a method is called within a Struct


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.


Solution

  • 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.