目录

Mockito

Mockito Tasty mocking framework for unit tests in Java

Mockito 是一个模拟框架,味道非常好。它使您可以使用干净简单的 API 编写漂亮的测试。Mockito 不会给您带来宿醉,因为测试非常易读,并且会产生干净的验证错误。

除了引入 mockito-core 外,还需要引入 mockito-junit-jupiter 扩展包。其中 mockito-inline 是对 Mockito 的增强,包括 mock final 类 static 方法。

1
2
3
4
5
6
// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation 'org.mockito:mockito-core:4.8.1'
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
testImplementation 'org.mockito:mockito-junit-jupiter:4.8.1'
// https://mvnrepository.com/artifact/org.mockito/mockito-inline
testImplementation 'org.mockito:mockito-inline:4.8.1'

如果使用 maven 为了单测能正常运行并被 mvn test 识别,还需要增加 maven-surefire-plugin。

在JUNIT5中,不再提供 @RunWith 注解,需要使用 @ExtendWith 注解。

为了让Mockito正常工作,需要在@ExtendWith注解中使用MockitoExtension.class

同时,@Before被@BeforeAll或者@BeforeEach替换,若使用@BeforeAll,需要写在静态方法上。

验证交互

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import static org.mockito.Mockito.*;

// mock creation
List mockedList = mock(List.class);

// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();

// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();

stub(存根) method 调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);

// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");

// the following prints "first"
System.out.println(mockedList.get(0));

// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
  • mock()/@Mock: 创建模拟
    • Answer/MockSettings 可选指定它的行为方式
    • when()/given() 指定模拟应该如何表现
    • 如果提供的 answers 不符合您的需求,请自己编写一个扩展 Answer 接口
  • spy()/@Spy:部分模拟,调用真正的方法,但仍然可以验证和存根
  • @InjectMocks: 使用 @Spy@Mock 注解自动注入 mocks/spies 字段
  • verify(): 检查使用given参数调用的方法
    • 可以使用灵活的参数匹配,例如通过 any()
    • 或捕获被 @Captor 调用的参数
  • BDDMockito 尝试使用行为驱动开发(Behavior-Driven development)语法

切记

  • 不要模拟你不拥有的类型
  • 不要模拟值对象
  • 不要嘲笑一切
  • 用你的测试表达爱!

基于注解的初始化

除了使用 Mockito.mock() 方法 mock 对象,推荐使用注解来完成。

1
2
// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation 'org.mockito:mockito-core:4.5.1'

Mockito 3 后 MockitoAnnotations.openMocks() 方法启用注解。Mockito 2 中 MockitoAnnotations.initMock() 被标记为 deprecated。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private AutoCloseable closeable;

@BeforeEach
public void open() {
  closeable = MockitoAnnotations.openMocks(this);
}

@AfterEach
void close() throws Exception {
  closeable.close();
}

Mockito JUnit 5 Extension

1
2
3
4
dependencies {
  // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
  testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1'
}

@ExtendWith(MockitoExtension.class) 标记类后可以使用注解自动 mock 对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionInjectMocksTest {

    @Mock
    private OrderRepository orderRepository;
    @InjectMocks
    private OrderService orderService;

    @Test
    void createOrderSetsTheCreationDate() {
        when(orderRepository.save(any(Order.class))).then(returnsFirstArg());

        Order order = new Order();
        Order savedOrder = orderService.create(order);
        
        assertNotNull(savedOrder.getCreationDate());
    }
}

@Mock

@Mock:模拟出一个Mock对象,对象是空的,需要指明对象调用什么方法,传入什么参数时,返回什么值

  • @Mock 来创建和注入模拟实例
  • Mockito.mock()
 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
public class ArticleManagerTest extends SampleBaseTestCase {

       @Mock private ArticleCalculator calculator;
       @Mock(name = "database") private ArticleDatabase dbMock;
       @Mock(answer = RETURNS_MOCKS) private UserProvider userProvider;
       @Mock(extraInterfaces = {Queue.class, Observer.class}) private ArticleMonitor articleMonitor;
       @Mock(stubOnly = true) private Logger logger;

       private ArticleManager manager;

       @Before public void setup() {
           manager = new ArticleManager(userProvider, database, calculator, articleMonitor, logger);
       }
   }

   public class SampleBaseTestCase {

       private AutoCloseable closeable;

       @Before public void openMocks() {
           closeable = MockitoAnnotations.openMocks(this);
       }

       @After public void releaseMocks() throws Exception {
           closeable.close();
       }
   }

Answer<T>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
when(mock.someMethod(anyString())).thenAnswer(
     new Answer() {
         public Object answer(InvocationOnMock invocation) {
             Object[] args = invocation.getArguments();
             Object mock = invocation.getMock();
             return "called with arguments: " + Arrays.toString(args);
         }
 });

 //Following prints "called with arguments: [foo]"
 System.out.println(mock.someMethod("foo"));

MockSettings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
   //Creates mock with different default answer and name
   Foo mock = mock(Foo.class, withSettings()
                                .defaultAnswer(RETURNS_SMART_NULLS)
                                .name("cool mockie")
                                );

   //Creates mock with different default answer, descriptive name and extra interfaces
   Foo mock = mock(Foo.class, withSettings()
                                .defaultAnswer(RETURNS_SMART_NULLS)
                                .name("cool mockie")
                                .extraInterfaces(Bar.class));

@Spy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
    List<String> spyList = Mockito.spy(new ArrayList<String>());
    
    spyList.add("one");
    spyList.add("two");

    Mockito.verify(spyList).add("one");
    Mockito.verify(spyList).add("two");

    assertEquals(2, spyList.size());

    Mockito.doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 public class Test{
    //Instance for spying is created by calling constructor explicitly:
    @Spy Foo spyOnFoo = new Foo("argument");
    //Instance for spying is created by mockito via reflection (only default constructors supported):
    @Spy Bar spyOnBar;
    private AutoCloseable closeable;
    @Before
    public void init() {
       closeable = MockitoAnnotations.openMocks(this);
    }
    @After
    public void release() throws Exception {
       closeable.close();
    }
    ...
 }

@InjectMocks

@InjectMocks:依赖@Mock对象的类,也即是被测试的类。@Mock出的对象会被注入到@InjectMocks对象中

 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
public class ArticleManagerTest extends SampleBaseTestCase {

       @Mock private ArticleCalculator calculator;
       @Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute
       @Spy private UserProvider userProvider = new ConsumerUserProvider();

       @InjectMocks private ArticleManager manager;

       @Test public void shouldDoSomething() {
           manager.initiateArticle();
           verify(database).addListener(any(ArticleListener.class));
       }
   }

   public class SampleBaseTestCase {

       private AutoCloseable closeable;

       @Before public void openMocks() {
           closeable = MockitoAnnotations.openMocks(this);
       }

       @After public void releaseMocks() throws Exception {
           closeable.close();
       }
   }

verify()

 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

 //using mock
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 //following two verifications work exactly the same - times(1) is used by default
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 //exact number of invocations verification
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost()
 verify(mockedList, atMostOnce()).add("once");
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

 //stubbing using anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");

 //following prints "element"
 System.out.println(mockedList.get(999));

 //you can also verify using argument matcher
 verify(mockedList).get(anyInt());

@Captor

参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
    List mockList = Mockito.mock(List.class);
    ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

    mockList.add("one");
    Mockito.verify(mockList).add(arg.capture());

    assertEquals("one", arg.getValue());
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test{

    @Captor ArgumentCaptor<AsyncCallback<Foo>> captor;

    private AutoCloseable closeable;

    @Before
    public void open() {
       closeable = MockitoAnnotations.openMocks(this);
    }

    @After
    public void release() throws Exception {
       closeable.close();
    }

    @Test public void shouldDoSomethingUseful() {
       //...
       verify(mock).doStuff(captor.capture());
       assertEquals("foo", captor.getValue());
    }
 }

Behavior Driven Development

行为驱动开发(英语:Behavior-driven development,缩写BDD)是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。BDD最初是由Dan North在2003年命名,它包括验收测试和客户测试驱动等的极限编程的实践,作为对测试驱动开发的回应。在过去数年里,它得到了很大的发展

编写测试的行为驱动开发风格使用 //given //when //then 注释作为测试方法的基本部分。

BDDMockito 类引入了一个别名,以便您使用given(Object)方法存根方法调用。

1
2
public class BDDMockito
extends Mockito {}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import static org.mockito.BDDMockito.*;

 Seller seller = mock(Seller.class);
 Shop shop = new Shop(seller);

 public void shouldBuyBread() throws Exception {
   //given
   given(seller.askForBread()).willReturn(new Bread());
   //when
   Goods goods = shop.buyBread();
   //then
   assertThat(goods, containBread());
 }

将 mock 注入 spy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
MyDictionary(Map<String, String> wordMap) {
    this.wordMap = wordMap;
}
@Mock
Map<String, String> wordMap; 

MyDictionary spyDic;

@Before
public void init() {
    MockitoAnnotations.initMocks(this);
    spyDic = Mockito.spy(new MyDictionary(wordMap));
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MockitoAnnotationsUninitializedUnitTest {

    @Mock
    List<String> mockedList;

    @Test(expected = NullPointerException.class)
    public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {
        Mockito.when(mockedList.size()).thenReturn(1);
    }
}

总结

Mockito 注解的注意事项

  • Mockito 的注解最大限度地减少了重复的模拟创建代码。
  • 它们使测试更具可读性。
  • @InjectMocks 是注入 @Spy 和 @Mock 实例所必需的。