Java应用使用Mockito进行模拟和测试桩
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java应用使用Mockito进行模拟和测试桩相关的知识,希望对你有一定的参考价值。
如果您觉得本博客的内容对您有所帮助或启发,请关注我的博客,以便第一时间获取最新技术文章和教程。同时,也欢迎您在评论区留言,分享想法和建议。谢谢支持!
相关阅读:
Java应用【六】Java 反射:动态类加载和调用教程
Java应用【七】使用Java实现数据结构和算法:排序、查找、图
Java应用【八】使用网络编程进行 socket 通信
Java应用【九】在 Java 中使用Log4j/Logback进行日志记录和调试
Mockito是一个流行的Java模拟框架,用于编写单元测试代码时模拟(mock)和测试桩(stub)对象的行为。可轻松模拟Java类和接口的行为,帮助测试人员和开发人员更好地设计和执行单元测试。
使用Mockito,开发人员可以模拟一个对象,使其表现出某些预期的行为,而无需使用真实对象。这种技术通常用于在不使用复杂的集成测试环境的情况下测试代码。Mockito可以协助进行单元测试、集成测试和行为驱动开发(BDD)。
一、Mockito基础知识
1、Mockito的优点
- 使用简单:Mockito的API简单明了,易于学习和使用。
- 支持多种场景:Mockito支持各种测试场景,如单元测试、集成测试和BDD等。
- 良好的文档:Mockito拥有全面的文档和用户群体,可以提供许多使用方案和实例。
2、Mockito的局限性
- 不支持静态方法和final方法的模拟。
- 可能会过度使用,导致测试代码的维护难度增加。
3、Mockito的常见概念
- Mock:指一个对象的虚拟实现,具有与真实对象相同的方法和属性,但不会真正执行其中的方法。
- Stub:指为某个方法调用提供预定义返回值的代码,通常用于控制测试中的代码路径。
- Verify:指验证Mock对象是否按照预期进行了交互。Verify可用于验证Mock对象的方法是否被调用了特定的次数,并且传入了预期的参数。
4、Mockito的常见用法
创建Mock对象
List mockList = mock(List.class);
Stub方法调用
when(mockList.get(0)).thenReturn("first");
验证方法调用
verify(mockList).add("one");
模拟方法抛出异常
when(mockList.get(anyInt())).thenThrow(new RuntimeException());
模拟连续调用
when(mockList.get(anyInt())).thenReturn("one", "two", "three");
Mockito提供了许多其他功能,如ArgumentMatchers用于匹配方法调用的参数、Annotations用于对Mock对象进行注释、Spy用于监视真实对象等等。通过学习和掌握Mockito的使用,可以更加高效地进行单元测试和集成测试。
二、使用Mockito进行模拟
1、使用Mockito进行模拟的步骤和示例
Mockito可以通过模拟对象来测试代码,步骤如下:
- 导入Mockito库。在pom.xml文件中添加以下依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
- 创建要测试的类和方法
public class UserService
private UserDao userDao;
public UserService(UserDao userDao)
this.userDao = userDao;
public User getUserById(int id)
return userDao.getUserById(id);
public interface UserDao
User getUserById(int id);
- 创建一个模拟对象
UserDao userDao = mock(UserDao.class);
- 设置模拟对象的行为
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
- 运行测试代码
@Test
public void testGetUserById()
UserDao userDao = mock(UserDao.class);
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
UserService userService = new UserService(userDao);
User user = userService.getUserById(1);
assertEquals(user.getId(), 1);
assertEquals(user.getName(), "John");
2、使用when()
Mockito的when()方法可以用于设置模拟对象的行为,例如:
when(mockObject.someMethod()).thenReturn(someValue);
示例代码:
@Test
public void testGetUserById()
UserDao userDao = mock(UserDao.class);
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
UserService userService = new UserService(userDao);
User user = userService.getUserById(1);
assertEquals(user.getId(), 1);
assertEquals(user.getName(), "John");
3、使用doReturn()
doReturn()方法与when()方法类似,可以用于设置模拟对象的行为,例如:
doReturn(someValue).when(mockObject).someMethod();
示例代码:
@Test
public void testGetUserById()
UserDao userDao = mock(UserDao.class);
doReturn(new User(1, "John")).when(userDao).getUserById(1);
UserService userService = new UserService(userDao);
User user = userService.getUserById(1);
assertEquals(user.getId(), 1);
assertEquals(user.getName(), "John");
4、使用mock()方法创建模拟对象
mock()方法可以用于创建模拟对象,例如:
SomeClass mockObject = mock(SomeClass.class);
示例代码:
@Test
public void testGetUserById()
UserDao userDao = mock(UserDao.class);
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
UserService userService = new UserService(userDao);
User user = userService.getUserById(1);
assertEquals(user.getId(), 1);
assertEquals(user.getName(), "John");
4、使用@Mock注解创建模拟对象
除了使用 mock()
方法创建模拟对象外,还可以使用 @Mock
注解来创建模拟对象。
首先需要在测试类中使用 @RunWith(MockitoJUnitRunner.class)
注解,以便在运行测试时自动初始化模拟对象。
接着在测试类中使用 @Mock
注解创建模拟对象,如下所示:
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest
@Mock
private UserDao userDao;
@Test
public void testGetUserById()
UserService userService = new UserService(userDao);
// ...
使用 @Mock
注解创建模拟对象时,需要注意以下几点:
- 被
@Mock
注解修饰的变量不能为 null
。 - 被
@Mock
注解修饰的变量默认为 @Mock(answer = RETURNS_DEFAULTS)
。 - 可以通过
@Mock(answer = Answers.RETURNS_SMART_NULLS)
显式指定返回智能 null
。
使用 @Mock
注解创建模拟对象时,Mockito 会自动创建并初始化模拟对象,并将其注入测试类中。
注意,使用 @Mock
注解创建的模拟对象需要在测试类中使用,否则会抛出 UnnecessaryStubbingException
异常。
5、使用@Spy注解进行模拟对象的部分模拟
除了使用@Mock注解创建一个完整的模拟对象之外,Mockito还提供了@Spy注解来创建部分模拟对象,这样可以在保留真实对象部分行为的同时,对其它行为进行模拟。
下面是@Spy注解的使用示例:
public class ExampleServiceTest
@Spy
private ExampleServiceImpl exampleServiceSpy;
@Test
public void testSomeMethod()
// 对exampleServiceSpy进行部分模拟,保留真实对象的部分行为
Mockito.doCallRealMethod().when(exampleServiceSpy).someMethod();
// 对someOtherMethod进行模拟
Mockito.when(exampleServiceSpy.someOtherMethod()).thenReturn("mocked result");
// 执行测试代码,调用exampleServiceSpy.someMethod()方法
exampleServiceSpy.someMethod();
// 验证someMethod()方法中是否调用了someOtherMethod()方法
Mockito.verify(exampleServiceSpy).someOtherMethod();
在这个例子中,我们使用了@Spy注解来创建一个ExampleServiceImpl对象的部分模拟。然后,我们使用了Mockito.doCallRealMethod().when(exampleServiceSpy).someMethod();
来保留exampleServiceSpy对象中someMethod()方法的真实行为,而对someOtherMethod()方法进行了模拟。最后,我们调用了exampleServiceSpy.someMethod()方法,并验证了someOtherMethod()方法是否被调用。
三、使用Mockito进行测试桩
1、测试桩的作用和场景
使用Mockito进行测试桩可以在单元测试中模拟方法的返回值或抛出异常,以便测试被测代码在各种情况下的行为。常见的使用场景包括:
- 测试被测代码在异常情况下的行为。通过测试桩可以模拟方法抛出异常的场景,测试被测代码在异常情况下是否能够正确地处理异常。
- 测试被测代码在特定情况下的行为。例如,测试一个方法在输入为null时的行为,可以使用测试桩模拟方法的输入为null的场景。
- 测试被测代码与外部依赖的交互。通过模拟外部依赖的返回值或抛出异常,可以测试被测代码在与外部依赖交互时的行为。
- 测试被测代码的边界条件。通过模拟外部依赖或方法的返回值,可以测试被测代码在各种边界情况下的行为,例如输入为最大值或最小值的情况。
总之,测试桩可以帮助开发人员创建各种测试场景,以确保被测代码的行为正确。
2、使用Mockito进行测试桩的步骤和示例
使用Mockito进行测试桩可以模拟需要的返回值或异常,以便在测试中测试需要的场景。以下是使用Mockito进行测试桩的步骤和示例:
- 创建需要进行测试桩的对象或接口:
public interface UserService
User getUserById(int userId);
- 使用Mockito进行测试桩,例如模拟getUserById方法返回指定的User对象:
@Test
public void testGetUserById()
UserService userService = Mockito.mock(UserService.class);
User expectedUser = new User("Alice", 20);
Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(expectedUser);
User actualUser = userService.getUserById(1);
Assert.assertEquals(expectedUser, actualUser);
在这个示例中,我们使用Mockito.mock()方法创建了一个UserService对象的模拟对象userService,并使用Mockito.when()方法对getUserById方法进行测试桩,指定了当传入任何整数时返回一个指定的User对象。然后我们使用模拟对象userService调用getUserById方法,并断言返回的User对象是否是我们期望的值。
- 使用测试桩模拟抛出异常:
@Test(expected = UserNotFoundException.class)
public void testGetUserByIdWhenUserNotFound()
UserService userService = Mockito.mock(UserService.class);
Mockito.when(userService.getUserById(Mockito.anyInt())).thenThrow(UserNotFoundException.class);
userService.getUserById(1);
在这个示例中,我们使用Mockito.when()方法对getUserById方法进行测试桩,指定了当传入任何整数时抛出一个UserNotFoundException异常。然后我们使用模拟对象userService调用getUserById方法,并期望捕获UserNotFoundException异常。
3、使用thenReturn()方法设置桩值
除了使用doReturn()方法进行桩方法之外,我们还可以使用thenReturn()方法来设置桩值。当桩方法返回一个值时,我们可以使用thenReturn()方法来指定这个值。例如:
@Test
public void testGetPerson()
// 创建模拟对象
PersonDao personDao = mock(PersonDao.class);
// 设置桩方法并返回模拟数据
when(personDao.getPerson(1)).thenReturn(new Person("Alice", 20));
// 执行被测试方法
PersonService personService = new PersonService(personDao);
Person person = personService.getPerson(1);
// 验证方法的返回值是否正确
assertEquals("Alice", person.getName());
assertEquals(20, person.getAge());
在这个例子中,我们通过when()方法设置桩方法,并使用thenReturn()方法指定了当getPerson()方法传入参数1时应该返回的模拟数据。然后,我们执行被测试的方法,并使用assertEquals()方法验证方法的返回值是否正确。如果方法返回了我们预期的模拟数据,那么测试就通过了。
4、使用thenThrow()方法抛出异常
Mockito 的 thenThrow() 方法可以用来设置测试桩方法在执行时抛出指定异常。这对于测试某些异常情况下的代码行为非常有用。
使用 thenThrow() 方法非常简单,只需要在桩方法后调用 thenThrow() 方法,并传入要抛出的异常类型即可。以下是一个示例:
@Test
public void testDoSomething() throws Exception
SomeObject mockObject = mock(SomeObject.class);
when(mockObject.doSomething()).thenThrow(new RuntimeException("test exception"));
// 确保调用 doSomething() 时会抛出 RuntimeException
assertThrows(RuntimeException.class, () ->
mockObject.doSomething();
);
在上面的示例中,我们使用 when()
方法对 doSomething()
方法进行桩,然后调用 thenThrow()
方法并传入一个 RuntimeException
对象。然后我们调用 doSomething()
方法,这时候会抛出一个运行时异常。最后,我们使用 JUnit 的 assertThrows()
方法来验证方法确实抛出了运行时异常。
总的来说,使用 thenThrow()
方法可以帮助我们测试代码在异常情况下的行为。
5、使用doAnswer()方法自定义桩方法
在某些情况下,可能需要自定义桩方法来满足测试的需要。这时可以使用Mockito的doAnswer()方法来实现。
doAnswer()方法如下:
public <T> OngoingStubbing<T> doAnswer(Answer<?> answer)
doAnswer()方法的参数是一个Answer对象,该对象表示自定义的桩方法的行为。Answer接口中有一个方法answer()
,该方法返回一个泛型对象,表示模拟方法的返回值。
下面是一个使用doAnswer()方法自定义桩方法的示例:
List<String> list = mock(List.class);
doAnswer(invocation ->
Object[] args = invocation.getArguments();
String result = (String) args[0] + "Mockito";
return result;
).when(list).get(anyInt());
这个例子中,我们自定义了List的get方法,将其返回值修改为输入参数的字符串后面加上"Mockito"。可以看到,在doAnswer()方法中,我们实现了Answer接口的answer()
方法,并使用Invocation对象来获取传入的参数和返回值。最后使用when()方法来应用桩方法。
6、使用@Captor注解进行参数捕获
Mockito提供了@Captor注解来捕获模拟对象方法调用中传入的参数。这个注解可以在测试用例中声明一个参数,并将其注解为@Captor,Mockito会自动将模拟对象方法调用中的参数注入到这个参数中,以便我们进行断言或其他操作。
使用@Captor注解进行参数捕获的步骤和示例如下:
- 在测试用例类中创建@Captor注解,并初始化一个参数,例如:
@Captor
private ArgumentCaptor<String> captor;
- 在测试用例中使用模拟对象调用方法,并将参数传递给模拟对象,例如:
mockObject.doSomething("test");
- 使用Mockito.verify()方法验证模拟对象方法的调用,并使用@Captor注解捕获方法调用时传递的参数,例如:
verify(mockObject).doSomething(captor.capture());
- 对捕获的参数进行断言或其他操作,例如:
assertEquals("test", captor.getValue());
这样,就可以使用@Captor注解进行参数捕获,方便我们在测试用例中对方法参数进行断言和其他操作。
四、Mockito进阶用法
1、使用Mockito进行异步测试
在异步编程中,我们经常需要对异步方法进行测试,确保它们能够按照预期工作。Mockito提供了一些方法来处理异步测试场景,包括异步回调和等待异步结果。
下面是使用Mockito进行异步测试的一些常见场景和示例。
模拟异步回调
在异步回调中,当一个异步操作完成时,它将调用一个回调函数来通知调用方。Mockito提供了Answer
接口,可以使用它来模拟异步回调函数。
示例:
@Test
public void testAsyncCallback()
MyAsyncService service = mock(MyAsyncService.class);
when(service.doSomethingAsync(anyString(), any(Consumer.class))).thenAnswer(new Answer<Void>()
@Override
public Void answer(InvocationOnMock invocation) throws Throwable
Object[] args = invocation.getArguments();
String arg1 = (String) args[0];
Consumer<String> callback = (Consumer<String>) args[1];
callback.accept(arg1 + " is done");
return null;
);
MyAsyncClient client = new MyAsyncClient(service);
String result = client.doSomething("test");
assertEquals("test is done", result);
在这个示例中,我们使用Answer
接口来模拟异步回调函数。当service.doSomethingAsync
方法被调用时,我们从参数中获取回调函数并执行它,然后返回null
。在测试中,我们验证异步客户端返回的结果是否正确。
等待异步结果
在异步编程中,我们经常需要等待异步操作完成后获取结果。为了测试异步方法,我们需要等待异步操作完成后再断言结果。Mockito提供了一些方法来处理这种场景。
示例:
@Test
public void testAsyncResult() throws Exception
MyAsyncService service = mock(MyAsyncService.class);
CompletableFuture<String> future = new CompletableFuture<>();
when(service.doSomethingAsync(anyString())).thenReturn(future);
MyAsyncClient client = new MyAsyncClient(service);
CompletableFuture<String> result = client.doSomethingAsync("test");
assertFalse(result.isDone()); // 验证异步方法还未完成
future.complete("test is done");
assertTrue(result.isDone()); // 验证异步方法已完成
assertEquals("test is done", result.get()); // 验证异步方法的结果是否正确
在这个示例中,我们使用CompletableFuture
来模拟异步方法的结果。当service.doSomethingAsync
方法被调用时,我们返回一个CompletableFuture
对象。在测试中,我们验证异步方法是否已经启动,然后手动完成CompletableFuture
对象并验证结果是否正确。
需要注意的是,在使用CompletableFuture
对象进行异步测试时,我们需要等待异步操作完成后再获取结果。我们可以使用isDone()
方法来判断异步操作是否完成,使用get()
方法来获取异步操作的结果。
2、使用Mockito进行参数匹配
在使用 Mockito 进行单元测试时,我们通常需要对被测方法传入不同的参数进行测试。但有时候我们希望只测试特定的参数组合,这时候就需要使用参数匹配。
Mockito 提供了一系列的参数匹配器,可以根据参数类型和值来匹配参数。常用的参数匹配器有:
- any():匹配任何对象,例如 any(String.class) 匹配任何 String 类型的参数。
- eq():匹配指定的对象,例如 eq("abc") 匹配参数值为 "abc" 的参数。
- isA():匹配指定类型的参数,例如 isA(String.class) 匹配参数类型为 String 的参数。
- anyXxx():匹配指定类型的基本数据类型,例如 anyInt() 匹配任何 int 类型的参数。
下面是使用 Mockito 进行参数匹配的示例代码:
// 定义被测类
public class UserService
private UserDao userDao;
public UserService(UserDao userDao)
this.userDao = userDao;
public User getUserByName(String name)
return userDao.getUserByName(name);
// 定义 UserDao 接口
public interface UserDao
User getUserByName(String name);
// 定义测试类
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest
@Mock
private UserDao userDao;
@InjectMocks
private UserService userService;
@Test
public void testGetUserByName()
// 设置桩方法
when(userDao.getUserByName(any(String.class)))
.thenReturn(new User("Tom"));
// 调用被测方法
User user = userService.getUserByName("Tom");
// 验证返回值
assertEquals("Tom", user.getName());
在上面的示例代码中,我们使用了 any() 方法来匹配 getUserByName() 方法的参数,这样就可以匹配任何字符串类型的参数,不需要具体指定参数值。这样可以使测试代码更加灵活和通用。
除了上面介绍的参数匹配器外,Mockito 还提供了很多其他的参数匹配器,具体可以参考 Mockito 的官方文档。
3、使用Mockito进行void方法的桩方法和验证
Mockito可以用于桩方法和验证void方法,下面将介绍如何使用Mockito来进行void方法的桩方法和验证。
void方法的桩方法
Mockito中有两种方法可以用于void方法的桩方法,分别是doNothing()和doThrow()。
- doNothing():表示当void方法被调用时,不做任何事情。
- doThrow():表示当void方法被调用时,抛出一个指定的异常。
下面是示例代码:
// 创建一个mock对象
List<String> mockList = mock(List.class);
// 对void方法进行桩方法,表示当调用add方法时,不做任何事情
doNothing().when(mockList).add(anyString());
// 对void方法进行桩方法,表示当调用clear方法时,抛出一个RuntimeException异常
doThrow(new RuntimeException()).when(mockList).clear();
void方法的验证
Mockito中使用verify()方法来验证void方法是否被调用,和之前提到的verify()方法类似,只是不需要设置返回值。
下面是示例代码:
// 创建一个mock对象
List<String> mockList = mock(List.class);
// 调用void方法
mockList.clear();
// 验证clear方法是否被调用过一次
verify(mockList).clear();
使用Mockito进行void方法的桩方法和验证和普通方法类似,只需要使用doNothing()、doThrow()方法进行桩方法,使用verify()方法进行验证即可。
4、使用Mockito进行mock静态方法和final方法
Mockito 无法直接 Mock 静态方法和 final 方法,因为它们不能被子类化和重载,但是 Mockito 可以与 PowerMock 等其他 Mock 框架结合使用来 Mock 静态方法和 final 方法。
PowerMock 是一个 Java 开源框架,它结合了 EasyMock 和 Mockito 的功能,并添加了对静态方法、final 方法、私有方法、构造函数和静态初始化块的支持。
下面是使用 PowerMock 和 Mockito 来 Mock 静态方法和 final 方法的步骤和示例:
1 在 Maven POM 文件中添加 PowerMock 和 Mockito 的依赖项:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.4</version>
<scope>test</scope>
</dependency>
2 使用 @RunWith(PowerMockRunner.class) 和 @PrepareForTest 注解来准备需要 Mock 的类:
@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassToMock.class)
public class MyClassTest
...
3 使用 PowerMockito.mockStatic(ClassToMock.class) 方法来 Mock 静态方法:
PowerMockito.mockStatic(ClassToMock.class);
Mockito.when(ClassToMock.staticMethod()).thenReturn(expectedValue);
4 使用 PowerMockito.whenNew(ClassToMock.class) 方法来 Mock 构造函数:
PowerMockito.whenNew(ClassToMock.class).withArguments(argument1, argument2).thenReturn(mockInstance);
5 使用 PowerMockito.spy(mockInstance) 方法来创建一个 Spy 对象:
ClassToMock mockInstance = PowerMockito.spy(new ClassToMock());
Mockito.when(mockInstance.finalMethod()).thenReturn(expectedValue);
6 使用 PowerMockito.doCallRealMethod().when(mockInstance).nonFinalMethod() 方法来 Mock 非 final 方法:
PowerMockito.doCallRealMethod().when(mockInstance).nonFinalMethod();
Mockito.when(mockInstance.nonFinalMethod()).thenReturn(expectedValue);
7 在测试方法中,使用 PowerMockito.verifyStatic(ClassToMock.class) 方法来验证静态方法调用,使用 PowerMockito.verifyNew(ClassToMock.class) 方法来验证构造函数调用:
PowerMockito.verifyStatic(ClassToMock.class);
ClassToMock.staticMethod();
PowerMockito.verifyNew(ClassToMock.class).withArguments(argument1, argument2);
需要注意的是,Mock 静态方法和 final 方法可能会影响代码的可维护性和可读性,应该尽量避免使用它们。只有在必要时才使用它们,并且应该选择适当的 Mock 框架来保持代码的简洁性和可读性。
五、总结
Mockito是一个流行的Java模拟框架,它可以帮助开发人员编写单元测试,以便更好地验证代码的正确性。Mockito提供了一些常用的方法,例如模拟对象、测试桩、参数匹配、异步测试等,这些方法可以大大简化测试代码的编写和维护。Mockito的优点包括易学易用、广泛支持、文档丰富等,但也存在局限性,例如不支持mock final方法等。对于开发人员而言,使用Mockito进行单元测试可以提高代码质量 以上是关于Java应用使用Mockito进行模拟和测试桩的主要内容,如果未能解决你的问题,请参考以下文章