当前位置: 代码网 > it编程>编程语言>Java > Java使用Mockito进行模拟和测试桩实践过程

Java使用Mockito进行模拟和测试桩实践过程

2026年04月26日 Java 我要评论
mockito是一个流行的java模拟框架,用于编写单元测试代码时模拟(mock)和测试桩(stub)对象的行为。可轻松模拟java类和接口的行为,帮助测试人员和开发人员更好地设计和执行单元测试。使用

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进行单元测试可以提高代码质量,降低代码维护成本,是一个非常值得掌握的技能。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com