JUnit知识点三则

@Mock@InjectMocks的区别

Difference Between @Mock and @InjectMocks

@Mock创建一个mock,@InjectMocks创建一个实例并且将注入由@Mock或者@Spy注解生成的mock。

注意,需要通过@Runwith(MockitoJUnitRunner.class)或者@Mockito.initMocks(this)来初始化这些mocks并注入。

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {

@InjectMocks
private SomeManager someManager;

@Mock
private SomeDependency someDependency; // this will be injected into someManager

//tests...

}

一般而言,是将需要测试的类,注解为@InjectMocks,将其依赖注解为@Mock

MockitoJUnitRunner.classSpringJUnit4ClassRunner.class的区别

[Using MockitoJUnitRunner.class instead of SpringJUnit4ClassRunner.class]

如果只是简单的没有依赖的单元测试,那么没有必要使用SpringJUnit4ClassRunner。这个Runner能够生成复杂的Spring Context。你可以在这个Context中定义你自己的Application context configuration。

SpringJUnit4ClassRunner比较适合集成测试(integration test purposes)。

ArgumentCaptor(参数捕获器)

学习Mocktio - 利用ArgumentCaptor(参数捕获器)捕获方法参数进行验证

利用参数捕获器来捕获传入方法的参数进行验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test  
public void argumentCaptorTest() {
List mock = mock(List.class);
List mock2 = mock(List.class);
mock.add("John");
mock2.add("Brian");
mock2.add("Jim");

ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);

verify(mock).add(argument.capture());
assertEquals("John", argument.getValue());

verify(mock2, times(2)).add(argument.capture());

assertEquals("Jim", argument.getValue());
assertArrayEquals(new Object[]{"Brian","Jim"},argument.getAllValues().toArray());
}

从实践中看,argument.capture()中捕获的是对象的引用,而不是复制对象,因而参数中如果对同一对象的两次使用,即便两次之中,对象发生了改变,捕获器也不能感知。如下文所示:

capturing previous values to verify mock object

利用ArgumentCaptor也可以用来断言日志输出,也就是mock一个appender,捕获appender的输入(即,log信息),利用ArgumentCaptor对日志进行重放,并断言,达到测试的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* @author chenxiaofeng
* @version 1.0
* @created 18/8/24.
*/
@Import({ TaskPersistenseService.class, ExecService.class })
@RunWith(MockitoJUnitRunner.class)
@Rollback
public class TaskFacadeTest {
@Mock
private TaskPersistenseService taskPersistenseService;

@Mock
private ExecService execService;

@InjectMocks
private TaskFacade taskFacade;

private static final Long MOCK_TASK_ID = 1L;
private static final Long MOCK_ROUTER_ID = 10L;

@Test
public void withOutExpExe() {
procedure(null);
verifyLastMessages("get taskId: 1, start run the task async");
}

private void procedure(Long timeMillis) {
when(taskPersistenseService.addNewTask(any(), anyInt())).thenReturn(MOCK_TASK_ID);
doNothing().when(execService).execTaskAsync(anyLong(), any());

TaskModel taskModel = new TaskModel();
taskModel.setRouterId(MOCK_ROUTER_ID);
taskModel.setExpExeMillis(timeMillis);

Assert.assertTrue(MOCK_TASK_ID.equals(taskFacade.submitTask(taskModel)));
}

@Mock
private Appender mockAppender;
@Captor
private ArgumentCaptor<LogEvent> captorLoggingEvent;

private Logger logger;

@Before
public void setup() {
// prepare the appender so Log4j likes it
when(mockAppender.getName()).thenReturn("MockAppender");
when(mockAppender.isStarted()).thenReturn(true);
//when(mockAppender.isStopped()).thenReturn(false);

logger = (Logger) LogManager.getRootLogger();
logger.addAppender(mockAppender);
logger.setLevel(Level.INFO);
}

@After
public void tearDown() {
// the appender we added will sit in the singleton logger forever
// slowing future things down - so remove it
logger.removeAppender(mockAppender);
}

// handy function to inspect the messages sent to the logger
private void verifyLastMessages(String message) {
verify(mockAppender, atLeastOnce()).append((LogEvent) captorLoggingEvent.capture());
//capture 只抓住了 LogEvent的引用,所以所有的值,都变成最后一次的 log
assertEquals(message, captorLoggingEvent.getValue().getMessage().getFormattedMessage());

}
}