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());
- 模拟连续调用
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");
}5、使用@mock注解创建模拟对象
除了使用 mock() 方法创建模拟对象外,还可以使用 @mock 注解来创建模拟对象。
首先需要在测试类中使用 @runwith(mockitojunitrunner.class) 注解,以便在运行测试时自动初始化模拟对象。
接着在测试类中使用 @mock 注解创建模拟对象,如下所示:
@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 异常。
6、使用@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 方法的步骤和示例:
在 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进行单元测试可以提高代码质量,降低代码维护成本,是一个非常值得掌握的技能。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论