[Power]Mockito使用和扩展

7 篇文章 0 订阅
1 篇文章 0 订阅

0

所有代码均可在线查看或本地克隆:mockito-shop
环境:

  • jdk 1.8
  • maven 3.3.9
  • sprint boot 2.1.6-RELEASE(junit 4.12 mockito 2.23.4 powemock 2.0.2)
  • IDEA Intellij IDEA 2017
  • lombok 1.18.10

参数匹配和返回值Mock

简单匹配

ArgumentMatchers工具类实现了很多常用的参数匹配器,比如anyString(); anyMap(); any(); any(Class<T>); nullable(Class<T>);eq(); startWith(); contains();notNull()等,但需要注意的是mockito 1.x和2.x在使用anyXXX()上是有区别的:

@RunWith(MockitoJUnitRunner.class)
public class SimpleArgumentMatcher {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserService userService;


    /**
     * anyLong() can not match null when using mockito 2.x(works well in mockito 1.x), use nullable(Long.class) instead.
     * delete @Ignore annotation for test
     */
    @Test
    @Ignore
    public void testSelectById () {
        Mockito.when(userService.selectById(Mockito.anyLong())).thenReturn(new User(0L, "mock-username", "mock-email"));
        User user = userController.userInfo(null);
        Assert.assertArrayEquals(
                new Object[]{0L, "mock-username", "mock-email"},
                new Object[]{user.getId(), user.getUsername(), user.getEmail()}
        );
    }


    @Test
    public void testSelectById_1 () {
        Mockito.when(userService.selectById(Mockito.nullable(Long.class))).thenReturn(new User(0L, "mock-username", "mock-email"));
        User user = userController.userInfo(null);
        Assert.assertArrayEquals(
                new Object[]{0L, "mock-username", "mock-email"},
                new Object[]{user.getId(), user.getUsername(), user.getEmail()}
        );
    }

}

上面的第一个单元测试在mockito 1.x运行良好,但在2.x中会出错,一般来说都是报空指针或者进了错误的待测代码分支。

集合匹配

和简单的基本类型匹配器一样,如果要匹配任意集合,可以使用anyList(); anyMap(); anySet(); anyCollection()等方法匹配,但如果需要匹配有几个元素等等,可能必须使用自定义的参数匹配器:

@RunWith(MockitoJUnitRunner.class)
public class CollectionArgumentMatcher {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test
    public void testAnyList () {
        Mockito.when(userDao.batchInsert(Mockito.anyList())).thenThrow(new MockitoException("test anyList()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("test anyList()");
        userService.batchInsert(Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com")));

    }

    @Test
    public void testListSize () {
        Mockito.when(userDao.batchInsert(Mockito.argThat(users -> users.size() == 2))).thenThrow(new MockitoException("test match list size"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("test match list size");
        userService.batchInsert(Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com")));

    }

    @Test
    public void testListType () {
        Mockito.when(userDao.batchInsert(Mockito.argThat(users -> users instanceof LinkedList))).thenThrow(new MockitoException("test match list type"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("test match list type");
        userService.batchInsert(new LinkedList<>(Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com"))));

    }
}

这里使用了argThat()方法,传入一个匿名的ArgumentMatcher,当然可以在把ArgumentMatcher定义在外部,方便复用。

有了argThat()方法后,感觉一切参数都可以被匹配到了,但很多时候还有其他更简单的方法。

对象匹配

如果仅仅使用前面出现过的方法,对象匹配有三种情况和相应的解决方案:

  • 任意对象:使用any()any(Claas<T>)方法即可
  • 部分字段或全部字段匹配:使用argThat()方法自定义参数匹配器
  • equals()方法匹配:使用eq()方法即可

第一种和第三种方法确实没错,但第二种可以有其他方法:

@RunWith(MockitoJUnitRunner.class)
public class ObjectArgumentMatcher {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test
    public void testAny () {
        Mockito.when(userDao.insert(Mockito.any())).thenThrow(new MockitoException("test any()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test any()"));
        userService.insert(new User( "test", "test@qq.com"));
    }

    @Test
    public void testAnyClass () {
        Mockito.when(userDao.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test any(Class<T>)"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test any(Class<T>)"));
        userService.insert(new User( "test", "test@qq.com"));
    }

    @Test
    public void testArgThat () {
        Mockito.when(userDao.insert(Mockito.argThat(new UsernameAndEmailMatcher()))).thenThrow(new MockitoException("test argThat()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test argThat()"));
        userService.insert(new User( "test11", "test@qq.com"));
    }

    @Test
    public void testRefEq () {
        User matchUser = new User(0L,"test", "test@qq.com");
        Mockito.when(userDao.insert(Mockito.refEq(matchUser, "id"))).thenThrow(new MockitoException("test refEq()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test refEq()"));
        userService.insert(new User( "test", "test@qq.com"));
    }

    @Test
    public void testEq () {
        User matchUser = new User("test", "test@qq.com");
        Mockito.when(userDao.insert(Mockito.eq(matchUser))).thenThrow(new MockitoException("test eq()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test eq()"));
        userService.insert(new User( "test", "test@qq.com"));
    }

    private static class UsernameAndEmailMatcher implements ArgumentMatcher<User> {
        @Override
        public boolean matches(User user) {
            return user.getUsername().contains("test") && user.getEmail().equals("test@qq.com");
        }
    }

}

其中refEq()方法反射比较,先创建一个待比较的对象,并且可以传入变长参数:忽略的字段列表,所有需要忽略的字段不参与反射取值和比较。

argThat()的自由度最高,每个字段可以自由使用任何其他匹配方式,比如上面的例子匹配username的时候使用的contains("test")而不是equals(),但其他方式的对相匹配只能是equals()

空对象匹配

可以使用nullable(Class<T>)匹配包含空对象的任意对象,如果要匹配null可以使用isNull()

@RunWith(MockitoJUnitRunner.class)
public class NullArgumentMatcher {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test
    public void testNullable () {
        Mockito.when(userDao.insert(Mockito.nullable(User.class))).thenThrow(new MockitoException("test nullable()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test nullable()"));
        userService.insert(new User( "test", "test@qq.com"));
    }

    @Test
    public void testNull () {
        Mockito.when(userDao.delete(Mockito.isNull())).thenThrow(new MockitoException("test isNull()"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test isNull()"));
        userService.delete(null);
    }

}

返回值为空

当一个方法的返回值为空的时候如何打桩Mock呢,一般情况下,如果对象被@Mock注解,它的所有public方法将被全部mock,有返回值的返回null,没有返回值的啥事儿也不做,所以如果没有返回值的情况下,如果mock该方法不做任何操作,可以仅仅使用@Mock注解即可,如果需要做其它事情,比如抛出异常、捕获参数并断言等,可以用下面的方法mock:

@RunWith(MockitoJUnitRunner.class)
public class ReturnVoidMock {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserService userService;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test
    public void testVoidMock () {
        Mockito.doNothing().when(userService).delete(Mockito.anyLong());
        userController.close(new User(){{setId(1L);}});
    }

    @Test
    public void testVoidMock_1 () {
        Mockito.doThrow(new MockitoException("test void throw")).when(userService).delete(Mockito.nullable(Long.class));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test void throw"));
        userController.close(new User());
    }

}

根据不同参数返回不同值

不可以在同一个测试方法中对同一个对象的同一对象mock两次,在mockito 2.x中会报空指针异常,并且提示第一次mock无效;那如果待测方法调了另一个对象的方法两次,并且两次的参数不一样,返回值也必须不一样,可以这样mock:

@RunWith(MockitoJUnitRunner.class)
public class ConditionMock {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    /**
     * java.lang.NullPointerException

     [MockitoHint] ConditionMock.testConditionReturn_error (see javadoc for MockitoHint):
     [MockitoHint] 1. Unused... -> at github.clyoudu.match.ConditionMock.testConditionReturn_error(ConditionMock.java:52)
     [MockitoHint]  ...args ok? -> at github.clyoudu.match.ConditionMock.testConditionReturn_error(ConditionMock.java:53)
     */
    @Test
    @Ignore
    public void testConditionReturn_error() {
        Mockito.when(userDao.insert(Mockito.argThat(user -> user.getUsername().equals("Bob")))).thenReturn(new User());
        Mockito.when(userDao.insert(Mockito.argThat(user -> user.getUsername().equals("David")))).thenThrow(new MockitoException("Username David not allowed to be registered"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("Username David not allowed to be registered");

        List<User> users = Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com"));
        userService.batchInsert2(users);
    }

    @Test
    public void testConditionReturn() {
        Mockito.when(userDao.insert(Mockito.any())).thenAnswer(invocationOnMock -> {
            User user = invocationOnMock.getArgument(0);
            switch (user.getUsername()) {
                case "Bob":
                    user.setId(999L);
                    return user;
                case "David":
                    throw new MockitoException("Username David not allowed to be registered");
                default:
                    return null;
            }
        });
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("Username David not allowed to be registered");

        List<User> users = Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com"));
        userService.batchInsert2(users);
        Assert.assertEquals(new Long(999), users.get(0).getId());
    }

    @Test
    public void testConditionReturn_1() {
        Mockito.when(userDao.insert(Mockito.any())).thenReturn(
                new User(999L, "Bob", "bob@aa.com"),
                new User(1000L, "David", "david@bb.com")
        );

        List<User> users = Arrays.asList(new User("Bob", "bob@aa.com"), new User("David", "david@bb.com"));
        Assert.assertEquals(2, userService.batchInsert2(users));
    }

}
  • 使用thenAnswer(),反射得到参数,根据不同参数返回不同结果即可。
  • 使用thenReturn()直接返回多个结果,如果thenReturn()返回多个结果,表示那么该方法被调用几次就返回第几个结果,如果调用次数超过返回结果数,则默认返回null

其中thenAnswer()更加灵活,因为可以对任意条件做任何事,包括修改参数、抛出异常、返回结果等,而thenReturn()只能返回结果。

Mock&Spy

正如上面提到过的,Mock注解会默认mock类的所有public方法,使没有返回值的方法什么事儿也不做、有返回值的方法返回null,但有时候我们并不想mock一些方法,比如这些方法没有依赖任何其他对象,默认mock显得多此一举,而@Spy注解刚好可以解决这个问题:

@RunWith(MockitoJUnitRunner.class)
public class SpyTest {

    @InjectMocks
    private UserController userController;

    @Spy
    private UserServiceImpl userService;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    /**
     * NPE when use
     *      @Mock
     *      private UserServiceImpl userService;
     * because getUsername() returns null 
     */
    @Test
    public void testSpy () {
        Mockito.doThrow(new MockitoException("test @Spy")).when(userService).insert(Mockito.any(User.class));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("test @Spy");
        User user = userController.register("aa@b.com");
        Assert.assertEquals("aa", user.getUsername());
    }

}

可以看到getUsername()调用了真实的方法,而insert(User)调用了mock方法,但注意使用@Spy时,声明mock的时候不再和@Mock相同,必须使用Mockito.doXXX().when(someObj).someMethod(someArgumentMather),原因是:

  • 使用@Mock注解的类,所有方法都不是真实的方法,而且返回值都是NULL。
  • 使用@Spy注解的类,所有方法都是真实方法,返回值都是和真实方法一样的。
  • 所以,你用when去设置模拟返回值时,它里面的方法(userService.insert(User))会先执行一次。
  • 使用doReturn或doThrow去设置的话,就不会产生上面的问题,因为有when来进行控制要模拟的方法,所以不会执行原来的方法。

Mock static/final/private/protected方法

mockito的原理是继承或实现原本的类,覆盖所有public方法默认全部返回null或空方法体,如果有设置某一个方法的期望,就按照期望来重写。显而易见,static方法无法被重写,那么mockito是不可能mock静态方法的,同理final private方法也不能被重写,因此这两种方法也不能被被mockitomock;而protected方法不能被对象实例直接调用,所以protected方法也不能直接使用mockito显示调用protected方法来mock,这几类方法只能使用powemock来mock,因为powermock的原理是修改类的class文件,比如去除final标识等,然后替换方法为虚拟实现,再把这个class用org.powermock.core.classloader.MockClassLoader加载,调用的时候使用MockClassLoader加载的类,而不是系统加载的类:

@RunWith(MockitoJUnitRunner.class)
public class MockitoFinalStaticTest {

    @Mock
    private IdGenerator idGenerator;

    /**
     * Also, this error might show up because you use argument matchers with methods that cannot be mocked.
     * Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
     * Mocking methods declared on non-public parent classes is not supported.
     */
    @Test
    public void testFinal () {
        Mockito.when(idGenerator.generated(Mockito.anyLong())).thenReturn(false);
        Assert.assertTrue(!idGenerator.generated(100L));
    }

    /**
     * Also, this error might show up because:
     * 1. you stub either of: final/private/equals()/hashCode() methods.
     *      Those methods *cannot* be stubbed/verified.
     *      Mocking methods declared on non-public parent classes is not supported.
     * 2. inside when() you don't call method on mock but on some other object.
     */
    @Test
    public void testStatic () {
        Mockito.when(IdGenerator.getId()).thenReturn(100L);
        Assert.assertEquals(new Long(100L), IdGenerator.getId());
    }

}

mockitomockstaticfinal方法的时候会报错。

@RunWith(PowerMockRunner.class)
@PrepareForTest(IdGenerator.class)
public class PowerMockitoTest {

    @Mock
    private IdGenerator idGenerator;

    @Spy
    private IdGenerator idGeneratorPower = new IdGenerator();

    @Before
    public void before() {
        PowerMockito.mockStatic(IdGenerator.class);
    }

    @Test
    public void testFinal () {
        PowerMockito.when(idGenerator.generated(Mockito.anyLong())).thenReturn(false);
        Assert.assertTrue(!idGenerator.generated(1L));

        PowerMockito.when(idGenerator.generated(Mockito.anyLong())).thenReturn(true);
        Assert.assertTrue(idGenerator.generated(1L));
    }

    @Test
    public void testStatic () {
        PowerMockito.when(IdGenerator.getId()).thenReturn(100L);
        Long id = IdGenerator.getId();

        PowerMockito.verifyStatic(IdGenerator.class, Mockito.times(1));
        IdGenerator.getId();
        Assert.assertEquals(new Long(100L), id);
    }

    @Test
    public void testPrivate () throws Exception {
        PowerMockito.doReturn(100L).when(idGeneratorPower, "getCurrentId1");
        Assert.assertEquals(new Long(100L), idGeneratorPower.getCurrentIdViaPrivate());
    }

    @Test
    public void testProtected () throws Exception {
        PowerMockito.doReturn(100L).when(idGeneratorPower, "getCurrentId2");
        Assert.assertEquals(new Long(100L), idGeneratorPower.getCurrentIdViaProtected());
    }

}

mockito的区别如下

  • 使用@RunWith(PowerMockRunner.class)
  • 使用@PrepareForTest(IdGenerator.class)
  • @Before方法里使用PowerMockito.mockStatic(IdGenerator.class);

正常情况下这些方法都不应该被mock,static final private protected方法都应算作内部契约,只有在这些方法很难被构造或者依赖于环境、无法生成固定值的时候才可能可以被mock。

断言(验证)

简单断言

形如

@RunWith(PowerMockRunner.class)
@PrepareForTest(IdGenerator.class)
public class SimpleAssert {

    @Before
    public void before() {
        PowerMockito.mockStatic(IdGenerator.class);
    }

    @Test
    public void testStatic () {
        PowerMockito.when(IdGenerator.getId()).thenReturn(100L);
        Assert.assertEquals(new Long(100L), IdGenerator.getId());
    }

}

这种断言就是简单断言,能用这种断言的方法有如下特点:

  • 有返回值;
  • 返回值为简单类型;
  • 只需要对返回值某一属性或特点断言

正常情况下,大部分断言都是类似的简单断言。

复杂对象断言

如果方法的返回值是一个对象,对象有很多字段,某个字段还是一个集合甚至是另外一个对象,这时候有很多种断言方法:

@RunWith(MockitoJUnitRunner.class)
public class ObjectAssert {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserService userService;

    @Test
    public void testSelectById_1 () {
        Mockito.when(userService.selectById(Mockito.nullable(Long.class))).thenReturn(new User(0L, "mock-username", "mock-email"));
        User user = userController.userInfo(null);
        Assert.assertEquals(new Long(0), user.getId());
        Assert.assertEquals("mock-username", user.getUsername());
        Assert.assertEquals("mock-email", user.getEmail());
    }

    @Test
    public void testSelectById_2 () {
        Mockito.when(userService.selectById(Mockito.nullable(Long.class))).thenReturn(new User(0L, "mock-username", "mock-email"));
        User user = userController.userInfo(null);
        Assert.assertArrayEquals(
                new Object[]{0L, "mock-username", "mock-email"},
                new Object[]{user.getId(), user.getUsername(), user.getEmail()}
        );
    }

    @Test
    public void testSelectById_3 () {
        Mockito.when(userService.selectById(Mockito.nullable(Long.class))).thenReturn(new User(0L, "mock-username", "mock-email"));
        User user = userController.userInfo(null);
        Assert.assertThat(user, new UserMatcher(new User(0L, "mock-username", "mock-email")));
    }

    private class UserMatcher extends BaseMatcher<User> {

        private User expectedUser;

        public UserMatcher(User expectedUser) {
            this.expectedUser = expectedUser;
        }

        @Override
        public boolean matches(Object o) {
            User actualUser = (User) o;
            return expectedUser.getId().equals(actualUser.getId()) &&
                    expectedUser.getUsername().equals(actualUser.getUsername()) &&
                    expectedUser.getEmail().equals(actualUser.getEmail());
        }

        @Override
        public void describeTo(Description description) {
            description.appendValue("Actual User is not match expected User");
        }
    }

}

断言异常

有时候我们需要捕获方法抛出的异常,并对异常断言以确保抛出了正确的异常,junit有很多种断言异常的方法:

@RunWith(MockitoJUnitRunner.class)
public class ExceptionAsserts {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserService userService;

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test
    public void testException () {
        try{
            Mockito.when(userService.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test exception"));
            userController.register(new User());
        } catch (Exception e) {
            Assert.assertTrue(e instanceof MockitoException);
            Assert.assertEquals("test exception", e.getMessage());
        }
    }

    @Test
    public void testException_1 () {
        try{
            Mockito.when(userService.insert(Mockito.any(User.class))).thenReturn(new User());
            userController.register(new User());
        } catch (Exception e) {
            Assert.assertTrue(e instanceof MockitoException);
            Assert.assertEquals("test exception", e.getMessage());
        }
    }

    @Test
    public void testException1 () {
        try{
            Mockito.when(userService.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test exception"));
            userController.register(new User());
            Assert.fail();
        } catch (Exception e) {
            Assert.assertTrue(e instanceof MockitoException);
            Assert.assertEquals("test exception", e.getMessage());
        }
    }

    @Test(expected = MockitoException.class)
    public void testException2 () {
        Mockito.when(userService.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test exception"));
        userController.register(new User());
    }

    @Test
    public void testException3 () {
        Mockito.when(userService.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test exception"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage("test exception");
        userController.register(new User());
    }

    /**
     * Error
     */
    @Test
    public void testException4 () {
        Mockito.when(userService.insert(Mockito.any(User.class))).thenThrow(new MockitoException("test exception extra"));
        expectedEx.expect(MockitoException.class);
        expectedEx.expectMessage(Is.is("test exception"));
        userController.register(new User());
    }

}
  • 对于testException_1testException_1,会有一个漏洞:当try调用方法没有抛出异常时,单元测试依然能通过,并没有进入catch块;
  • testException1可以弥补第一种方法的不足:在方法调用后使用Assert.fail()可以保证当测试没有进入catch块时会报断言错误,测试不通过;
  • 一般来说,单元测试不应该使用条件分支和流程控制,治理如果直接使用try catch不是很优雅的测试方法,可以使用testException2@Test中指定expected异常类型,但只能断言异常类型,并不能对message断言;
  • testException3完美解决了上面两个问题,是比较推荐的异常断言方式,但注意在断言message时,只要message包含声明的字符串都会返回成功;
  • testException4演示了如何断言meesage与声明的字符串完全匹配,当然还有更多的匹配方式,也可以自定义匹配方式。

断言参数

有时候一个方法没有返回值,或者我们对返回值并不感兴趣,并且这个方法的参数是调用其他方法生成的,如何断言这个方法的参数是否正确呢?可能有点绕,直接看测试:

@RunWith(MockitoJUnitRunner.class)
public class ParameterAsserts {

    @InjectMocks
    private UserController userController;

    @Spy
    private UserServiceImpl userService;

    @Captor
    private ArgumentCaptor<User> userArgumentCaptor;

    @Test
    public void testParameter () {
        Mockito.doReturn(new User()).when(userService).insert(Mockito.any(User.class));
        userController.register("aaa@bb.com");
        Mockito.verify(userService).insert(userArgumentCaptor.capture());
        Assert.assertEquals("aaa", userArgumentCaptor.getValue().getUsername());
    }

}

这里需要断言调用insert(String email)方法有没有正确获取用户名,而有必须mockinsert(User)方法,导致insert(User)方法的返回值不能作为断言的依据,只能去要捕获insert(User)参数,对该参数断言,判断获取的用户名是否正确。

断言方法调用次数

有时候只需要断言方法是否被调用或者只关心方法是否被调用时,可以这样断言:

@RunWith(MockitoJUnitRunner.class)
public class VerifyAsserts {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @Test
    public void testVerifyTimes () {
        userService.batchInsert2(new ArrayList<>());
        Mockito.verify(userDao, Mockito.never()).insert(Mockito.any(User.class));
    }

    @Test
    public void testVerifyTimes1 () {
        Mockito.when(userDao.insert(Mockito.any(User.class))).thenReturn(new User());
        userService.batchInsert2(Collections.singletonList(new User()));
        Mockito.verify(userDao, Mockito.times(1)).insert(Mockito.any(User.class));
        Mockito.verify(userDao, Mockito.atMost(1)).insert(Mockito.any(User.class));
        Mockito.verify(userDao, Mockito.atLeast(1)).insert(Mockito.any(User.class));
    }

}

扩展

其实PowerMockitoMockito的扩展,两者又可以非常放便地和JUnit结合使用,这得益于JUnit的Runner模式:使用@RunWith()注解指定特定的Runner,就可以按照Runner中设定的方式加载、初始化、运行单元测试。

那么基于这个Runner模式我们能不能自定义一个Runner来解决目前JUnitPowerMockitoMockito都无法解决的问题呢,我觉得是可以的:

  • 断言异常非常不方便:继承JUnit默认的Runner,增强一下对@Test注解的反射支持,比如重新定义一个@ExceptionTest注解,可以在注解中定义异常类型和message;
  • 参数外置话做的不是很好:很多时候我们会对同一个代码分支用大量的测试用例去验证,但遵循单元测试中不应该有流程控制和循环,并且一个单元测试最好只有一个断言的原则,多少个用例就意味着多少个测试方法,但这些方法几乎是一样的方法体。即使不遵循这样的原则,多个断言也让单元测试难以维护,那么可以扩展一个Runner,让其支持参数外置化,这样的Runner其实已经有实现,具体参考:【单元测试】JUnit参数化测试的讨论

一般来说,任何JUnit单元测试不支持的情况,或者使用JUnit难以遵循单元测试原则的,都可以尝试使用自定义Runner解决这个问题。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值