//<== = t_t = t_t = t_t = t_t = t_t 2. Most of the time, I will use English to avoid ambiguity in Chinese

In the first article, we mentioned that unit tests that return void methods tend to verify that a method on an object inside is called. In that article, I used the example of a login method in an activity:

public void login() { String username = ... //get username from username EditText String password = ... //get password from password EditText //do other operation like validation, etc ... mUserManager.performLogin(username, password); }Copy the code

The unit test for the login method should be to call the login method in the Activity and verify that the performLogin method of mUserManager is called. But to use an Activity, we need to use the Robolectric framework, which we haven’t talked about yet. So in this article, we assume that this code is stored in a LoginPresenter (LoginPresenter) class, which is a pure Java class, and that the username and password are passed in from outside:

public class LoginPresenter { private UserManager mUserManager = new UserManager(); public void login(String username, String password) { if (username == null || username.length() == 0) return; if (password == null || password.length() < 6) return; mUserManager.performLogin(username, password); }}Copy the code

From the previous JUnit article, it was easy to write a unit test for the login() method:

public class LoginPresenterTest { @Test public void testLogin() throws Exception { LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("xiaochuang", "xiaochuang password"); // Verify that the LoginPresenter mUserManager performLogin() method is called with the parameters "xiaochuang", "xiaochuang password"... }}Copy the code

Now, the key question is, how do you verify that the mUserManager performLogin() method in LoginPresenter is called and that its arguments are correct? If you already knew from the first article in this series that mocks are needed here, let’s introduce mocks.

Mock concept: Two misunderstandings

The concept of a Mock is very simple, as we explained earlier: Mock is to create a Mock object of a class that can be used to replace the real object in a test environment to achieve two main purposes:

  1. Verify that certain methods of this object are called, how many times are called, what are the arguments, and so on
  2. Specifies the behavior of some method on this object, returns a specific value, or performs a specific action

To use mocks, you usually need a Mock framework. For this article, we’ll use Mockito, one of the most widely used mocks in the Java world.

Mock UserManager (userManager.class); mock UserManager (userManager.class); mock UserManager (userManager.class); After mocking the UserManager class, we can start testing:

public class LoginPresenterTest { @Test public void testLogin() { Mockito.mock(UserManager.class); // LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("xiaochuang", "xiaochuang password"); // Verify that the LoginPresenter mUserManager performLogin() method is called with the following parameters: "xiaochuang", "xiaochuang password"... }}Copy the code

However, we need to validate the mUserManager object in LoginPresenter, but we can’t get the object because mUserManager is private. Without thinking too much, let’s simply remove the burst point and add a getter to LoginPresenter, and you’ll see why I’m doing this now.

public class LoginPresenter { private UserManager mUserManager = new UserManager(); public void login(String username, String password) { if (username == null || username.length() == 0) return; if (password == null || password.length() < 6) return; mUserManager.performLogin(username, password); } public UserManager getUserManager() { // return mUserManager; }}Copy the code

Ok, now we can verify that mUserManager is called:

public class LoginPresenterTest { @Test public void testLogin() throws Exception { Mockito.mock(UserManager.class); LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("xiaochuang", "xiaochuang password"); UserManager userManager = loginPresenter.getUserManager(); // // verify that userManager's performLogin() method is called with "xiaochuang", "xiaochuang password"... }}Copy the code

It’s time to explain how to validate a call to a method on an object. Mockito: mockito.verify (objectToVerify).methodtoverify (arguments); ObjectToVerify and methodToVerify are the objects and methods you want to verify, respectively. Mockito.verify(userManager).performLogin(“xiaochuang”, “xiaochuang password”); Ok, now let’s put this line of code into the test:

public class LoginPresenterTest { @Test public void testLogin() throws Exception { Mockito.mock(UserManager.class); LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("xiaochuang", "xiaochuang password"); UserManager userManager = loginPresenter.getUserManager(); Mockito.verify(userManager).performLogin("xiaochuang", "xiaochuang password"); / /}}Copy the code

Then we ran the test and found, well… Wrong:

Mockito.verify(userManager).performLogin(“xiaochuang”, “xiaochuang password”); . This error basically means that the argument passed to mockito.verify () must be a mock object, and we passed it not a mock object, so we failed.

Mock () does not mock an entire class. Instead, it mocks an object belonging to that class from a class passed in and returns that mock object. The class itself has not changed, and the object that comes out of this new class has not changed at all!

With the above example, mockito.mock (userManager.class); It simply returns a mock object belonging to the UserManager class. The UserManager class itself is unaffected, and LoginPresenter’s mUserManager from new UserManager() is a normal object, not a mock object. The argument to mockito.verify () must be a mock object, that is, Mockito can only validate mock object method calls. So that’s the wrong way to write it.

Mockito.mock(userManager.class); Object to verify, the code is as follows:

public class LoginPresenterTest { @Test public void testLogin() throws Exception { UserManager mockUserManager = Mockito.mock(UserManager.class); // LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("xiaochuang", "xiaochuang password"); Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password"); / /}}Copy the code

On a run, it turns out, uh… Wrong again:

The error message goes something like this: We want to verify that the performLogin() method of mockUserManager is called, but it isn’t.

This is the second misconception I want to explain about mocks: mock objects don’t automatically replace objects in formal code; you have to have some way of applying mock objects to formal code

MockUserManager = mockito.mock (userManager.class); We did create a mock object for us, stored in the mockUserManager. However, when we call loginPresenter. Login (“xiaochuang”, “xiaochuang password”); The mUserManager used is still the normal object created using new UserManager(). MockUserManager doesn’t get any calls, so when we verify that its performLogin() method is called, we fail.

For this problem, it is obvious that we must replace the mUserManager reference with the mock object referenced by mockUserManager before calling loginPresby.login (). The easiest way to do this is to add a setter:

public class LoginPresenter { private UserManager mUserManager = new UserManager(); public void login(String username, String password) { if (username == null || username.length() == 0) return; if (password == null || password.length() < 6) return; mUserManager.performLogin(username, password); } public void setUserManager(UserManager userManager) { // this.mUserManager = userManager; }}Copy the code

In the meantime, we don’t need the getter anymore, so we just delete it. Then according to the above ideas, write out the test code is as follows:

@Test
public void testLogin() throws Exception {
    UserManager mockUserManager = Mockito.mock(UserManager.class);
    LoginPresenter loginPresenter = new LoginPresenter();
    loginPresenter.setUserManager(mockUserManager);  //

    loginPresenter.login("xiaochuang", "xiaochuang password");

    Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");
}
Copy the code

Last run, Hu… Finally!

Of course, if you don’t use that setter anywhere in your official code, adding a method just for testing isn’t a very elegant solution, and a better solution is to use dependency injection, The UserManager is passed in as an argument to the LoginPresenter constructor. Look forward to the next article, where we will focus on the concept of mocks and the use of Mockito.

However, I could not help but want to say: elegance is elegance, whether it is necessary or not is worth it, but it is another matter. In general, I think it’s worth it, because it makes the class testable, which means we can verify the correctness of the class, and it gives us a guarantee to reconstruct the class later, to prevent mistakes from changing the class, and so on. So, a lot of times, if you want to do unit tests, you have to add some extra code to some classes. Go ahead! After all, elegance is not food, but solving problems and fixing bugs is, and making excellent, bug-free products is even better, so Just Do It!

Ok, now that I think you have a good idea of mocks and how to use them, we can focus on Mockito’s functionality and usage.

The use of Mockito

1. Validate method calls

Mockito.verify(mockUserManager).performLogin(“xiaochuang”, “xiaochuang password”); This is used to verify that performLogin() for mockUserManager is called with “xiaochuang “and “xiaochuang password” arguments. It would be more accurate to say that this line of code verifies that the performLogin() method of the mockUserManager gets a call. Mockito.verify(mockUserManager, mockito.times (1)).performLogin(“xiaochuang”, “xiaochuang password”); Mockito.times(1). So, if you want to verify that a method on an object is called multiple times, just pass the count to mockito.times (). Mockito.verify(mockUserManager, Mockito.times(3)).performLogin(…) ; // Verify that performLogin from mockUserManager gets three calls.

For the verification of the number of calls, in addition to the fixed number of calls, it can also verify the most, least never, etc., the methods are as follows: Static import Mockito static import Mockito static import Mockito static import Mockito static import Mockito static import Mockito The prefix. I’ll follow that rule for the rest of this article. (I’ve been meaning to say this for a long time, but I just never found the right time.)

A lot of times you don’t care what the parameters of the called method are, or you don’t know, you just care that the method gets called. In this case, Mockito provides a series of any methods to represent any argument: Mockito.verify(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString()); AnyString () indicates that anyString can be used. Null? That’s ok! AnyString, anyInt, anyLong, anyDouble, etc. AnyObject represents any object, and any(clazz) represents any object belonging to Clazz. At the time of writing this article, I have just discovered that there are also very interesting and very human anyCollection, anyCollectionOf(Clazz), anyList(Map, set), anyListOf(Clazz), etc. Looks like I wrote a lot of bad code.

2. Specify the behavior of certain methods of the mock object

So far, we’ve covered one of the main uses of mocks: validating method calls. Mocks have two main uses. The second is to specify the return value of a method or to perform a specific action.

So let’s move on to the second most important use of mocks, the first of which is to specify that a method of a mock object returns a particular value. Now assume that our LoginPresenter login method is implemented as follows:

public void login(String username, String password) { if (username == null || username.length() == 0) return; / / assume we have a certain request for password strength, using a dedicated the validator to verify the validity of the password if (mPasswordValidator. VerifyPassword (password)) return; // mUserManager.performLogin(null, password); }Copy the code

Here, we have a PasswordValidator to validate passwords, but the class’s verifyPassword() method takes a long time to run, such as networking. At this point in the testing environment we want to keep it simple and specify that it returns either true or false directly. You might be thinking, is that ok? Really? The answer is yes, because we’re testing the login() method, which has nothing to do with the internal logic of PasswordValidator and is the true granularity of unit testing. When (mockObject.targetMethod(args)). ThenReturn (desiredReturnValue); mockito.when (mockObject.targetMethod(args)). This should be easy to understand, considering the PasswordValidator example above:

MockValidator = mockito.mock (passwordValidator.class); mockValidator = mock(passwordValidator.class); // Call the mockValidator verifyPassword method and pass "xiaochuang_is_handsome" Returns true Mockito. When (mockValidator. VerifyPassword (" xiaochuang_is_handsome ")). ThenReturn (true); // When you call the verifyPassword method of mockValidator and pass "xiaochuang_is_not_handsome", Returns false Mockito. When (the validator. VerifyPassword (" xiaochuang_is_not_handsome ")). ThenReturn (false);Copy the code

Similarly, you can use the any series of methods to specify “return XXX regardless of any parameter value passed in” :

// Returns true when the mockValidator verifyPassword method is called, Mockito.when(validator.verifypassWord (anyString())).thenReturn(true);Copy the code

Specifying a method to return a specific value is covered here, but for more detailed and advanced usage you can Google it yourself. Next, how to specify a method to perform a specific action is usually used when the target method is of type void. Now suppose our LoginPresenter login() method looks like this:

public void loginCallbackVersion(String username, String password) { if (username == null || username.length() == 0) return; / / assume we have a certain request for password strength, using a dedicated the validator to verify the validity of the password if (mPasswordValidator. VerifyPassword (password)) return; // The result of login will be passed back through callback. mUserManager.performLogin(username, password, new NetworkCallback() { // @Override public void onSuccess(Object data) { //update view with data } @Override public void onFailure(int code, String msg) { //show error msg } }); }Copy the code

Here, we want to further test to mUserManager performLogin NetworkCallback code inside, was updated to verify the view, and so on. In the test environment, we don’t want to rely on mUserManager. PerformLogin real logic, but to make mUserManager direct call incoming NetworkCallback onSuccess or onFailure method. Mockito.doanswer (desiredAnswer).when(mockObject).targetMethod(args); What is passed to doAnswer() is an Answer object in which we implement what action we want to perform. Combine the above examples to explain:

Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { // Here you can get the Object[] arguments = Invocation to performLogin (); NetworkCallback callback = (NetworkCallback) arguments[2]; callback.onFailure(500, "Server error"); return 500; } }).when(mockUserManager).performLogin(anyString(), anyString(), any(NetworkCallback.class));Copy the code

Here, the code in answer is executed when the performLogin method of mockUserManager is called. In our example above, the onFailure method of the callback is called directly. Pass both the onFailure method 500 and “Server error”.

Of course, using mockito.doanswer () requires the creation of an Answer object, which is a bit cumbersome and the code looks tedious. If you want to simply specify the target method to “doNothing”, you can use mockito.donothing (). If you want to specify the target method to “throw an exception,” you can use mockito.dothrow (desiredException). If you want the target method to call real logic, use mockito.docallrealMethod (). (what??? Isn’t that the default? ?? No!)

Spy

One last thing about Spy. We’ve covered the two main functions of mock objects, but if you’re wondering what happens if I don’t specify the behavior of a method, the second is to specify the behavior of a method. Now to add, if not specified, all non-void methods of a mock object return default values: int, long methods return 0, Boolean methods return false, object methods return NULL, and so on; The void method does nothing. Many times, however, you want to call the default implementation of the object unless specified, while still having the ability to validate method calls. This is exactly what a Spy object can do. Creating a Spy object and its usage are described as follows:

Public class PasswordValidator {public Boolean verifyPassword(String password) {return "xiaochuang_is_handsome".equals(password); }} @test public void testSpy() {// It is similar to creating a mock, except that the spy method is called instead of the mock method. PasswordValidator spyValidator = mockito.spy (passwordValidator.class); / / by default, the spy object invokes the real logic of this class, and returns the corresponding return value, it can control the real logic spyValidator. VerifyPassword (" xiaochuang_is_handsome "); //true spyValidator.verifyPassword("xiaochuang_is_not_handsome"); / / false / / spy Mockito method can also specify a particular behaviors. The when (spyValidator. VerifyPassword (anyString ())). ThenReturn (true); / / in the same way, you can verify spy object method invocation of spyValidator. VerifyPassword (" xiaochuang_is_handsome "); Mockito.verify(spyValidator).verifyPassword("xiaochuang_is_handsome"); //pass }Copy the code

In short, the only difference between spy and mock is that the default behavior is different: Spy objects’ methods default to call real logic, while mock objects’ methods default to do nothing or return default values.

summary

This article introduces the concept of mock and the use of Mockito. Perhaps many other methods of Mockito are not introduced, but this is just a matter of reading the document, and more importantly, understanding the concept of mock. If you want to learn more about the use of Mockito refer to this article, it is quite well written.

In the next article we will introduce the concept of dependency injection, and (perhaps) using Dagger2 to make it easier to do dependency injection, as well as using it in unit testing. There are still a lot of mistakes to be aware of. Stay tuned!

At the end of Github, if you are interested in Android unit test, welcome to join our communication group, because there are more than 100 members of the group, we can not scan the code to join, please pay attention to the public account below to get the method of joining.