XCTestCase to check if a method is called within a Struct
Asked Answered
C

1

22

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.

Chenoweth answered 19/5, 2016 at 11:23 Comment(3)
I don't understand what you mean. Could you elaborate on your question? I have added an example of code that one could use if they were using a class to demonstrate what I am trying to achieve. However I am using a struct and thus can't inherit and override the method. I am unsure how to do a similar test to the one I have written using a struct instead of a class.Chenoweth
This sounds like a good approach, but I don't understand what makes KeychainAccess a struct. Is it in your own project? If that's the limiting factor, promoting that to a class might be what you need so you can override the func as shown. Note that I don't have a similar example in my own projects.Muley
@bneely. Thanks for your comment. Yes it's in my own project. However I realise I have named it badly and will be refactoring. It's more of a AuthorizationAccess class/struct that wraps around methods for doing Oauth and keychain saving. Yeah I am starting to think that the only way to have these tests is to use a class.Chenoweth
B
29

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.

Bryanbryana answered 21/5, 2016 at 17:0 Comment(3)
Hi dasdom, thanks for the response. Sorry for late reply. I have edited my answer to show an example scenario. I would like to use protocol but I am not fully understanding how to create the mock object and have it call the protocol method.Chenoweth
I have added an example.Bryanbryana
Very helpful indeed. One other question. It seems by having the MockDataApi have a method that mutates the struct you need the protocol to be mutating and that means the real struct needs to handle mutating. i.e - it must be a var and not let. I don't like have mutating functions, I prefer returning a new struct to avoid mutating state and thus would prefer to keep my protocol free from the mutating keyword. However for testing purposes what are your thoughts on the mock object being a class instead which conforms to the protocol? So: class MockDataApi: DataApiProtocol { }Chenoweth

© 2022 - 2024 — McMap. All rights reserved.