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)
}
})
}
}
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With