Mockito

Liverpoolfc.tv: github.com/mockito/moc…

Introduction to the

Mock is to create a mock object of a class and, in a test environment, replace the real object with the following:

  • Verify that a method of this object is called, such as how many times it is called, what the arguments are, and so on;
  • Specifies the behavior of certain methods of this object, such as returning specific values, performing specific actions, and so on.

To use mocks, you generally need a Mock framework, and this article introduces Mockito, one of the most widely used Mock frameworks in the Java world.

For example, test user login:

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

Corresponding test classes:

public class LoginPresenterTest { LoginPresenter loginPresenter; @Before public void setUp() throws Exception { loginPresenter = new LoginPresenter(); } @Test public void testLogin() { UserManager mockUserManager = Mockito.mock(UserManager.class); // The UserManager used in loginPresenter must be a Mock object to validate userManager. performLogin. / / so I need to mock object to loginPresenter loginPresenter. SetUserManager (mockUserManager); loginPresenter.login("aya", "123456"); // Verify passes in objects that must be mock out, Mockito.verify(mockUserManager).performLogin(" AYA ", "123456"); }}Copy the code

Pay attention to

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

    For example, it would be wrong to write:

    public class LoginPresenterTest { @Test public void testLogin() { Mockito.mock(UserManager.class); UserManager userManager = loginPresenter.getUserManager(); loginPresenter.login("aya", "123456"); Mockito.verify(userManager).performLogin("aya", "123456"); }}Copy the code
  2. The mock object does not automatically replace the object in the formal code. You must have some way to apply the mock object to the formal code. In the above example, we passed the mock object using setUserManager(). So this is also a false example:

    public class LoginPresenterTest { @Test public void testLogin(){ UserManager mockUserManager = Mockito.mock(UserManager.class); LoginPresenter loginPresenter = new LoginPresenter(); loginPresenter.login("aya", "123456"); Mockito.verify(mockUserManager).performLogin("aya", "123456"); }}Copy the code
  3. Passing in a setter as a mock object is not very elegant if the setter method is not used elsewhere but is added for testing purposes. This can be done using dependency injection methods, such as taking UserManager as an argument to LoginPresenter’s constructor, which ends as follows:

    public class LoginPresenter { private UserManager mUserManager; public LoginPresenter(UserManager userManager){ mUserManager = 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 class LoginPresenterTest { LoginPresenter loginPresenter; @Test public void testLogin() { UserManager mockUserManager = Mockito.mock(UserManager.class); // The UserManager used in loginPresenter must be a Mock object to validate userManager. performLogin. LoginPresenter = new loginPresenter (mockUserManager); loginPresenter.login("aya", "123456"); // Verify passes in objects that must be mock out, Mockito.verify(mockUserManager).performLogin(" AYA ", "123456"); }}Copy the code

Validate method calls

Check the Mockito source code to see that verify() also has an overloaded method:

   /**
    * Verifies certain behavior happened at least once / exact number of times / never. E.g:
    * <pre class="code"><code class="java">
    *   verify(mock, times(5)).someMethod("was called five times");
    *
    *   verify(mock, atLeast(2)).someMethod("was called at least two times");
    *
    *   //you can use flexible argument matchers, e.g:
    *   verify(mock, atLeastOnce()).someMethod(<b>anyString()</b>);
    * </code></pre>
    *
    * <b>times(1) is the default</b> and can be omitted
    * <p>
    * Arguments passed are compared using <code>equals()</code> method.
    * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed.
    * <p>
    *
    * @param mock to be verified
    * @param mode times(x), atLeastOnce() or never()
    *
    * @return mock object itself
    */
   @CheckReturnValue
   public static <T> T verify(T mock, VerificationMode mode) {
       return MOCKITO_CORE.verify(mock, mode);
  }
Copy the code

VerificationMode source code is as follows:

/** * Allows verifying that certain behavior happened at least once / exact number * of times / never. E.g: * * <pre class="code"><code class="java"> * verify(mock, times(5)).someMethod(&quot; was called five times&quot;) ; * * verify(mock, never()).someMethod(&quot; was never called&quot;) ; * * verify(mock, atLeastOnce()).someMethod(&quot; was called at least once&quot;) ; * * verify(mock, atLeast(2)).someMethod(&quot; was called at least twice&quot;) ; * * verify(mock, atMost(3)).someMethod(&quot; was called at most 3 times&quot;) ; * * </code></pre> * * <b>times(1) is the default</b> and can be omitted * <p> * See examples in javadoc for {@link Mockito#verify(Object, VerificationMode)} */ public interface VerificationMode { /** * Performs the verification */ void verify(VerificationData data); /** * Description will be prepended to the assertion error if verification fails. * @param description The custom Failure message * @return VerificationMode * @since 2.1.0 */ VerificationMode description(String description); }Copy the code

As you can see, when validating method calls, you can specify the number of calls using times(), never(), atLeastOnce(), atLeast() and atMost().

If you don’t care about the arguments passed in and only whether the method is called, Mockito also provides a series of any methods to represent any arguments:

Mockito.verify(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString());
Copy the code

Mockito.anystring () represents any non-null string, AnyList,anyListOf(Class

clazz),anySet(),anyMap(),anyCollection, etc. Even isNotNull(),isNotNull(Class

clazz),anyObject, more methods to see the source code.

Specify the return value

Suppose the LoginPresenter login() method above is implemented like this:

public void login(String username, String password) { if (username == null || username.length() == 0) return; mPasswordValidator = new PasswordValidator(); if (! mPasswordValidator.verifyPassword(password)) return; mUserManager.performLogin(username, password); }Copy the code

When logging in, you need to verify the correctness of the password, which may require network authentication, so it will be time-consuming. This can be done easily during testing, such as returning true or false directly. Because you’re testing the login() method and have nothing to do with the internal logic of validating passwords, you can do this, which is the granularity of unit tests.

Specifying a method of a mock object that returns a specific value is written like this:

Mockito.when(mockObject.targetMethod(args)).thenReturn(desiredReturnValue);
Copy the code

So we can write this:

MockValidator = mockito.mock (passwordValidator.class); mockValidator = mock(passwordValidator.class); // When you call the mockValidator verifyPassword method with "123" passed in, Returns true Mockito. When (mockValidator. VerifyPassword (" 123 ")). ThenReturn (true); // When you call the mockValidator verifyPassword method with "123456" passed in, Returns false Mockito. When (the validator. VerifyPassword (" 123456 ")). ThenReturn (false);Copy the code

Since the return value has been specified, the real logic of the verifyPassword() method is not executed!

At this point, the test class can be written like this:

public class LoginPresenterTest { LoginPresenter loginPresenter; @Test public void testLogin() { UserManager mockUserManager = Mockito.mock(UserManager.class); PasswordValidator mPasswordValidator = Mockito.mock(PasswordValidator.class); Mockito.when(mPasswordValidator.verifyPassword(Mockito.anyString())).thenReturn(true); // Test fails when thenReturn(false) Because mUserManager. PerformLogin (username, password) code won't execute loginPresenter = new loginPresenter (mockUserManager, mPasswordValidator); loginPresenter.login("aya", "123"); Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("aya", "123"); }}Copy the code

Note: The mock object mPasswordValidator must be passed in!

Performing a specific action

Suppose our LoginPresenter login() method looks like this:

public void login(String username, String password) {
       if (username == null || username.length() == 0) return;
       if (!mPasswordValidator.verifyPassword(password)) return;
       mUserManager.performLogin(username, password, new UserManager.NetCallback() {
           @Override
           public void onSuccess(Object data) {
               //登陆成功,用数据更新UI
​
          }
​
           @Override
           public void onFailure(String msg) {
               //登陆失败,显示msg
​
          }
      });
  }
Copy the code

Here, we want to further test to mUserManager performLogin NetCallback 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 NetCallback 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:

public class LoginPresenterTest { LoginPresenter loginPresenter; @Test public void testLogin() { UserManager mockUserManager = Mockito.mock(UserManager.class); PasswordValidator mPasswordValidator = Mockito.mock(PasswordValidator.class); Mockito.when(mPasswordValidator.verifyPassword(Mockito.anyString())).thenReturn(true); loginPresenter = new LoginPresenter(mockUserManager, mPasswordValidator); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { // Here you can get the Object[] arguments = Invocation to performLogin (); UserManager.Net callback = (UserManager.Net callback) arguments[2]; callback.onFailure("404 Not found"); return 404; } }).when(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString(), Mockito.any(UserManager.NetCallback.class)); loginPresenter.login("aya", "123456"); }}Copy the code

When the performLogin method of mockUserManager is called, the code in answer is executed. Our example above calls the onFailure method of the callback directly, passing it along with the onFailure method 404 and “Not found”.

In addition to doAnswer(), mockito provides doNothing(), which specifies the target method to “doNothing”; DoThrow (desiredException), which specifies that the target method “throws an exception”; DoCallRealMethod (), which makes the target method call real logic.

Spy

For a mock object, we can specify a return value and perform a specific action, or we can omit it. Otherwise, all non-void methods of a mock object will return default values: Int, long methods return 0, Boolean methods return false, object methods return NULL, and so on. The void method does nothing.

If you want to execute the specified action when specified, call the default implementation of the object when not specified, and still have the ability to validate method calls. Then you can use mockito.spy () to create objects.

Creating a Spy object and its usage are described as follows:

public class PasswordValidator { public boolean verifyPassword(String password) { if (password == null || password.length() < 6) { return false; } return true; } } public class PasswordValidatorTest { @Test public void testSpy(){ PasswordValidator mockPasswordValidator = Mockito.mock(PasswordValidator.class); boolean b0 = mockPasswordValidator.verifyPassword("1234567"); Println (" Test mock default behavior: "+ b0); // If not specified, mock returns the default value system.out.println (" Test mock default behavior:" + b0); Mockito.when(mockPasswordValidator.verifyPassword("1234567")).thenReturn(true); / / the mock object specified behavior: return true Boolean b1. = mockPasswordValidator verifyPassword (" 1234567 "); Println (" Test mock behavior: "+ b1); // The mock object returns the specified value system.out.println (" Test mock behavior:" + b1); PasswordValidator spyPasswordValidator = Mockito.spy(PasswordValidator.class); boolean b2 = spyPasswordValidator.verifyPassword("1234567"); Println (" Test spy default behavior: "+ b2); Mockito.when(spyPasswordValidator.verifyPassword("1234567")).thenReturn(false); / / spy object specified behavior: returns false Boolean b3. = spyPasswordValidator verifyPassword (" 1234567 "); Println (" Test spy specified behavior: "+ b3); Mockito.verify(spyPasswordValidator).verifyPassword("1234567"); / / test PasswordValidator. VerifyPassword (" 1234567 ") call number, the results failed, because had invoked the above two PasswordValidator. VerifyPassword (" 1234567 ")}}Copy the code

Run the test method testSpy() with the following output:

As you can see from this comparison, the only difference between spy and mock is the default behavior: Spy objects’ methods invoke real logic by default, while mock objects’ methods either do nothing by default or return default values.

Now that we’ve covered the use of Mockito, the next article will cover PowerMock.