using mockito for unit test

One common problem faced when unit testing is how to test one object when it is dependant on another object. You could create instances of both the object under test and the dependent object and test them both together, however testing aggregated objects is not what unit testing is about – unit tests should test individual objects for their correct behaviour, not aggregations of objects! Moreover, this approach just won’t work if the dependent object hasn’t even been implemented yet.

A common technique for handling dependencies in unit tests is to provide a surrogate, or “mock” object, for the dependent object instead of a real one. The mock object will implement a simplified version of the real objects methods that return predictable results and can be used for testing purposes.

The drawback of this approach is that in a complex application, you could find yourself creating a lot of mock objects. This is where frameworks like Mockito can save you a lot of time and effort.

A Test Scenario

To demonstrate what you can do with Mockito, we’ll examine how we might test an Account object that is dependant on a data access object (AccountDAO).  The account object can calculate the charges applicable in the current month by using the DAO to count the number of days overdrawn and then performing a simple calculation on the value obtained from that call. The method names on the classes we’ll use are self-explanatory:

AccountAndDAO
To test this thoroughly, we should test two things:

  1. that the account obtains the number of overdrawn days by calling the correct method on the DAO,
  2. that the account calculates the correct fees based upon the value it gets back from the call.

Testing that the account calls the correct method on the DAO

Using JUnit to run the test case, we could write something like this:

01.import static org.mockito.Mockito.*;
02.
03.public class TestAccount {
04.@Test
05.public void checkAccountCallsDaoMethods() {
06.//create the object under test
07.Account account = new Account();
08.
09.//create a mock DAO
10.AccountDAO mockedDao = mock(AccountDAO.class); 
11.
12.//associate the mocked DAO with the object under test
13.account.setDAO(mockedDao);
14.
15.//call the method under test
16.long charge = account.calculateCharges();
17.
18.//verify that the 'countOverdrawnDaysThisMonth' was called
19.verify(mockedDao).countOverdrawnDaysThisMonth();
20.}
21.}

Taking centre stage in all this is the Mockito class which has a bunch of static methods that are used within the unit test (note the static import on line 1).

The call to the ‘mock’ method (line 10) creates a mock object that can be used in place of a real AccountDAO. The call to ‘verify’ (line 19), will throw an exception if the ‘countOverdrawnDaysThisMonth’ method was not called on the mock object.

It is worth noting here that this is a simple example which just demonstrates the basic principle of verification, it is also possible to use the verify method to check for parameter values and ranges in the method call too.

Testing that the account performs its calculations correctly

In the above example, we mocked the DAO but didn’t specify what any of the mocked methods should do. In this case, all methods will return sensible default values of: null, zero or false. More commonly we need to specify something other than these defaults when writing our tests:

01.import static org.mockito.Mockito.*;
02.import static junit.framework.Assert.*;
03.import org.junit.*;
04.
05.public class TestAccount {
06.private Account account;
07.
08.@Before
09.public void setup() {
10.AccountDAO mockedDao = mock(AccountDAO.class);
11.
12.//specify that this method on the mock object should return 8
13.when(mockedDao.countOverdrawnDaysThisMonth()).thenReturn(8);
14.
15.account = new Account();
16.account.setDAO(mockedDao);
17.}
18.
19.@Test
20.public void checkChargesWhenOverdrawn() {
21.//call the method under test
22.long charge = account.calculateCharges();
23.
24.//assert that the charge was £2 per day overdrawn
25.assertEquals(charge, 16);
26.}
27.}

The statement on line 13 specifies that whenever the ‘countOverdrawnDaysThisMonth’ is called it should return a value of 8; overriding the default of zero.

Given that the correct charge is two pounds (or dollars) per day overdrawn, then the assertion on line 25 will pass if the account performed the correct calculation (8 x 2 = 16 right).

 

 

创建Mock对象

可以对类和接口进行Mock对象的创建,创建的时候可以为Mock对象命名,也可以忽略命名参数。为Mock对象命名的好处就是调试的时候会很方便。比如,我们Mock多个对象,在测试失败的信息中会把有问题的Mock对象打印出来,有了名字我们可以很容易定位和辨认出是哪个Mock对象出现的问题。另外它也有限制,对于final类、匿名类和Java的基本类型是无法进行Mock的。除了用Mock方法来创建模拟对象,如mock(Class<T> classToMock),也可以使用@mock注解定义Mock,下面我们通过实例来介绍一下如何创建一个Mock对象。

Java代码  收藏代码
  1. import org.junit.Test;
  2. import org.mockito.Mock;
  3. import com.baobaotao.domain.User;
  4. import com.baobaotao.service.UserService;
  5. import com.baobaotao.service.UserServiceImpl;
  6. import static org.junit.Assert.*;
  7. import static org.mockito.Mockito.*;
  8. import org.mockito.MockitoAnnotations;
  9. public class MockitoSampleTest{
  10.     //① 对接口进行模拟
  11.     UserService mockUserService = mock(UserService.class);
  12.     //② 对类进行模拟
  13.     UserServiceImpl mockServiceImpl = mock(UserServiceImpl.class);
  14. //③ 基于注解模拟类
  15. @Mock
  16. User mockUser;
  17.     @Before
  18.     public void initMocks() {
  19.     //④ 初始化当前测试类所有@Mock注解模拟对象
  20.         MockitoAnnotations.initMocks(this);
  21.     }
  22.      …
  23.  }

在①处和②处,通过Mockito提供的mock()方法创建UserService 用户服务接口、用户服务实现类UserServiceImpl的模拟对象。在③处,通过@Mock注解创建用户User类模拟对象,并需要在测试类初始化方法中,通过MockitoAnnotations.initMocks()方法初始化当前测试类中所有打上@Mock注解的模拟对象。如果没有执行这一步初始化动作,测试时会报模拟对象为空对象异常。

设定Mock对象的期望行为及返回值

从上文中我们已经知道可以通过when(mock.someMethod()).thenReturn(value)来设定Mock对象的某个方法调用时的返回值,但它也同样有限制条件:对于static和final修饰的方法是无法进行设定的。下面我们通过实例来介绍一下如何调用方法及设定返回值。

Java代码  收藏代码
  1. import org.junit.Test;
  2. import org.mockito.Mock;
  3. import com.baobaotao.domain.User;
  4. import com.baobaotao.service.UserService;
  5. import com.baobaotao.service.UserServiceImpl;
  6. public class MockitoSampleTest {
  7.      …
  8.      //① 模拟接口UserService测试
  9.     @Test
  10.     public void testMockInterface() {
  11.           //①-1 对方法设定返回值
  12.     when(mockUserService.findUserByUserName(“tom”)).thenReturn(
  13.                 new User(“tom”, “1234”));
  14. //①-2 对方法设定返回值
  15. doReturn(true).when(mockServiceImpl).hasMatchUser(“tom”, “1234”);
  16.  //①-3 对void方法进行方法预期设定
  17. User u = new User(“John”, “1234”);
  18.     doNothing().when(mockUserService).registerUser(u);
  19. //①-4 执行方法调用
  20.         User user = mockUserService.findUserByUserName(“tom”);
  21.         boolean isMatch = mockUserService.hasMatchUser(“tom”,”1234″);
  22.          mockUserService.registerUser(u);
  23.         assertNotNull(user);
  24.         assertEquals(user.getUserName(), “tom”);
  25.         assertEquals(isMatch, true);
  26.     }
  27.     //② 模拟实现类UserServiceImpl测试
  28. @Test
  29.     public void testMockClass() {
  30.           // 对方法设定返回值
  31.         when(mockServiceImpl.findUserByUserName(“tom”))
  32. .thenReturn(new User(“tom”, “1234”));
  33.     doReturn(true).when(mockServiceImpl).hasMatchUser(“tom”, “1234”);
  34.         User user = mockServiceImpl.findUserByUserName(“tom”);
  35.         boolean isMatch = mockServiceImpl.hasMatchUser(“tom”,”1234″);
  36.         assertNotNull(user);
  37.         assertEquals(user.getUserName(), “tom”);
  38.         assertEquals(isMatch, true);
  39.     }
  40.     //③ 模拟User类测试
  41. @Test
  42.     public void testMockUser() {
  43.         when(mockUser.getUserId()).thenReturn(1);
  44.         when(mockUser.getUserName()).thenReturn(“tom”);
  45.         assertEquals(mockUser.getUserId(),1);
  46.         assertEquals(mockUser.getUserName(), “tom”);
  47.     }

在①处,模拟测试接口UserService的findUserByUserName()方法、hasMatchUser()方法及registerUser()方法。在①-1处通过when().thenReturn()语法,模拟方法调用及设置方法的返回值,实例通过模拟调用UserService 用户服务接口的查找用户findUserByUserName()方法,查询用户名为“tom”详细的信息,并设置返回User对象:new User(“tom”, “1234”)。在①-2处通过doReturn (). when ()语法,模拟判断用户hasMatchUser()方法的调用,判断用户名为“tom”及密码为“1234”的用户存在,并设置返回值为:true。在①-3处对void方法进行方法预期设定,如实例中调用注册用户registerUser()方法。设定调用方法及返回值之后,就可以执行接口方法调用验证。在②处和③处,模拟测试用户服务实现类UserServiceImpl,测试的方法与模拟接口一致。

验证交互行为

Mock对象一旦建立便会自动记录自己的交互行为,所以我们可以有选择地对其交互行为进行验证。在Mockito中验证mock对象交互行为的方法是verify(mock). xxx()。于是用此方法验证了findUserByUserName()方法的调用,因为只调用了一次,所以在verify中我们指定了times参数或atLeastOnce()参数。最后验证返回值是否和预期一样。

Java代码  收藏代码
  1. import org.junit.Test;
  2. import org.mockito.Mock;
  3. import com.baobaotao.domain.User;
  4. import com.baobaotao.service.UserService;
  5. import com.baobaotao.service.UserServiceImpl;
  6. public class MockitoSampleTest {
  7.      …
  8.      //① 模拟接口UserService测试
  9.     @Test
  10.     public void testMockInterface() {
  11.           …
  12.     when(mockUserService.findUserByUserName(“tom”))
  13.                          .thenReturn(new User(“tom”, “1234”));
  14. User user = mockServiceImpl.findUserByUserName(“tom”);
  15. //①-4 验证返回值
  16.         assertNotNull(user);
  17.         assertEquals(user.getUserName(), “tom”);
  18.         assertEquals(isMatch, true);
  19.  //①-5 验证交互行为
  20. verify(mockUserService).findUserByUserName(“tom”);
  21. //①-6 验证方法至少调用一次
  22. verify(mockUserService, atLeastOnce()).findUserByUserName(“tom”);
  23. verify(mockUserService, atLeast(1)).findUserByUserName(“tom”);
  24. //①-7 验证方法至多调用一次
  25. verify(mockUserService, atMost(1)).findUserByUserName(“tom”);
  26.     }

Mockio为我们提供了丰富调用方法次数的验证机制,如被调用了特定次数verify(xxx, times(x))、至少x次verify(xxx, atLeast (x))、最多x次verify(xxx, atMost (x))、从未被调用verify(xxx, never())。在①-6处,验证findUserByUserName()方法至少被调用一次。在①-7处,验证findUserByUserName()方法至多被调用一次。

 

 

 

FROM HERE and HERE

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s