Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject mock class into method to unit test method

Tags:

ios

swift

I'm trying to unit test a method which has a dependency on another class. The method calls a class method on that class, essentially this:

func myMethod() {

     //do stuff
     TheirClass.someClassMethod()

}

Using dependency injection technique, I would like to be able to replace "TheirClass" with a mock, but I can't figure out how to do this. Is there some way to pass in a mock class (not instance)?

EDIT: Thanks for the responses. Perhaps I should have provided more detail. The class method I am trying to mock is in an open source library.

Below is my method. I am trying to test it, while mocking out the call to NXOAuth2Request.performMethod. This class method issues a network call to get the authenticated user's info from our backend. In the closure, I am saving this info to the global account store provided by the open source library, and posting notifications for success or failure.

func getUserProfileAndVerifyUserIsAuthenticated() {

    //this notification is fired when the refresh token has expired, and as a result, a new access token cannot be obtained
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "didFailToGetAccessTokenNotification", name: NXOAuth2AccountDidFailToGetAccessTokenNotification, object: nil)

    let accounts = self.accountStore.accountsWithAccountType(UserAuthenticationScheme.sharedInstance.accountType) as Array<NXOAuth2Account>
    if accounts.count > 0 {
        let account = accounts[0]

        let userInfoURL = UserAuthenticationScheme.sharedInstance.userInfoURL

        println("getUserProfileAndVerifyUserIsAuthenticated: calling to see if user token is still valid")
        NXOAuth2Request.performMethod("GET", onResource: userInfoURL, usingParameters: nil, withAccount: account, sendProgressHandler: nil, responseHandler: { (response, responseData, error) -> Void in

            if error != nil {
                println("User Info Error: %@", error.localizedDescription);
                NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
            }
            else if let data = responseData {
                var errorPointer: NSError?
                let userInfo = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &errorPointer) as NSDictionary

                println("Retrieved user info")
                account.userData = userInfo

                NSNotificationCenter.defaultCenter().postNotificationName("UserAuthenticated", object: self)
            }
            else {
                println("Unknown error retrieving user info")
                NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
            }
        })
    }
}
like image 493
Mike Taverne Avatar asked Jan 23 '15 18:01

Mike Taverne


People also ask

How do you mock another method that is being tested in the same class?

We can mock runInGround(String location) method inside the PersonTest class as shown below. Instead of using mock(class) here we need to use Mockito. spy() to mock the same class we are testing. Then we can mock the method we want as follows.

How do you inject a class in JUnit?

Use constructor injection when using @Assisted injection To use it, annotate the implementation class' constructor and the fields that aren't known by the injector: And later: AssistedInject maps the create() method's parameters to the corresponding @Assisted parameters in the implementation class' constructor.

How do you mock and inject a mock?

Mockito @InjectMocks annotations allow us to inject mocked dependencies in the annotated class mocked object. This is useful when we have external dependencies in the class we want to mock. We can specify the mock objects to be injected using @Mock or @Spy annotations.


1 Answers

In Swift this is better done by passing a function. There are many ways to approach this, but here is one:

func myMethod(completion: () -> Void = TheirClass.someClassMethod) {
    //do stuff
    completion()
}

Now you can pass a completion handler, while existing code will continue to use the default method. Notice how you can refer to the function itself (TheirClass.someClassMethod). You don't have to wrap it up in a closure.

You may find it better to let the caller just pass this all the time rather than making it a default. That would make this class less bound to TheirClass, but either way is fine.

It's best to integrate this kind of loose coupling, design-for-testability into the code itself rather than coming up with clever ways to mock things. In fact, you should ask yourself if myMethod() should really be calling someClassMethod() at all. Maybe these things should be split up to make them more easily tested, and then tie them together at a higher level. For instance, maybe myMethod should be returning something that you can then pass to someClassMethod(), so that there is no state you need to worry about.

like image 52
Rob Napier Avatar answered Oct 19 '22 08:10

Rob Napier