被测试类
public class Example {
public String doSomething() {
System.out.println("doSomething");
return "hello";
}
}
Spy和Mock的区别
针对@Spy:
when(...) thenReturn(...)
做了真实调用。只是返回了指定的结果doReturn(...) when(...)
不做真实调用
针对@Mock: 整个对象被mock
两者的区别:mock方法和spy方法都可以对对象
进行mock。但是前者是接管了对象的全部方法,而后者只是将有桩实现(stubbing)的调用进行mock,其余方法仍然是实际调用。
怎么看使用的是哪个框架
下面说的用哪个测试框架,主要是看@Test是哪个框架的!!!
junit4
默认运行器(无需显式指定)
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
//@RunWith(BlockJUnit4ClassRunner.class) // JUnit 4 默认使用 BlockJUnit4ClassRunner 来运行测试。这种情况下,你不需要显式添加 @RunWith 注解。
public class Example5Test {
@Test
public void testSomething() {
System.out.println("This is a test");
}
}
@RunWith 指定特殊运行器
当你需要增强测试功能或与外部框架集成时,可以使用 @RunWith 并指定运行器。
以下是常见基于 @RunWith 的第三方集成场景(在 JUnit 4 中):
- Mockito: 使用 @RunWith(MockitoJUnitRunner.class),自动注解初始化。
- Spring: 使用 @RunWith(SpringJUnit4ClassRunner.class) 来整合 Spring 框架。
- JUnit Parameterized: 使用 @RunWith(Parameterized.class) 执行参数化测试。
- Cucumber: 使用 @RunWith(Cucumber.class) 来运行 BDD 测试。
- 自定义 Runner: 用于添加自定义测试行为。
在 JUnit 5 中,@RunWith 被替换为 @ExtendWith,功能更加灵活且模块化。
@RunWith+mockito框架
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class Example6Test {
@Spy
private Example eSpy = new Example();
@Test // 这个是org.junit.Test
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// doSomething (来自doSomething方法的真实调用里打印的日志)
// ni hao
}
}
加载SpringBoot上下文
import org.example.springbootdemo.spring.SpringExample;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
// SpringRunner vs SpringJUnit4ClassRunner
//区别:
//SpringRunner 是 SpringJUnit4ClassRunner 的现代别名,它们的行为和功能几乎完全一致。
//推荐使用 SpringRunner.class,特别是在 Spring Boot 项目中,这是社区标准和趋势。
//适用场景:
//如果你是 Spring Boot 项目,优先选择 @RunWith(SpringRunner.class)。
//如果你是传统 Spring MVC 项目,仍然可以沿用 @RunWith(SpringJUnit4ClassRunner.class),也是完全有效的。
@RunWith(SpringRunner.class) // 或者 @RunWith(SpringJUnit4ClassRunner)
@SpringBootTest // 启动完整 SpringBoot 应用上下文
public class Spring1Test {
@Autowired
private SpringExample springExample;
@Test
public void test1() {
springExample.voidFun();
// 输出:
// hello
}
}
SpringBoot+@MockBean
import org.example.springbootdemo.spring.SpringExample;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.mockito.Mockito.when;
@RunWith(SpringRunner.class) // 或者 @RunWith(SpringJUnit4ClassRunner)
@SpringBootTest // 启动完整 SpringBoot 应用上下文
public class Spring2Test {
@MockBean // 替换 Spring 容器中的 springExample bean
private SpringExample springExample;
@Test
public void test1() {
springExample.voidFun();
// 没有任何输出
}
@Test
public void test2() {
when(springExample.doSomething()).thenReturn("hi");
System.out.println(springExample.doSomething());
// 输出:
// hi
}
}
juint5+mockito
场景一:@ExtendWith+@Spy 或 Mockito.spy()
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) // JUnit 5 可以使用扩展 @ExtendWith(MockitoExtension.class) 自动将@Spy的对象初始化为spy对象
public class Example1Test {
@Spy
private Example eSpy = new Example();
@Test
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// doSomething (来自doSomething方法的真实调用里打印的日志)
// ni hao
}
@Test
public void test3() {
// 这两句手动方式 等效于上面的 @Spy + @ExtendWith(MockitoExtension.class) 的方式
Example example = new Example();
Example exampleSpy = Mockito.spy(example);
// ----- 以上两句 ----
doReturn("hi").when(exampleSpy).doSomething();
System.out.println(exampleSpy.doSomething());
// 输出:
// hi
}
}
场景二:@BeforeEach+@Spy
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
public class Example2Test {
@Spy
private Example eSpy = new Example();
@BeforeEach // juint 5 的注解
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// doSomething (来自doSomething方法的真实调用里打印的日志)
// ni hao
}
}
场景三:@ExtendWith+@Mock
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) // JUnit 5 可以使用扩展 @ExtendWith(MockitoExtension.class) 自动将@Spy的对象初始化为spy对象
public class Example3Test {
@Mock
private Example eSpy = new Example();
@Test
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// ni hao
}
}
场景四:@BeforeEach+@Mock
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
public class Example4Test {
@Mock
private Example eSpy = new Example();
@BeforeEach // juint 5 的注解
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// ni hao
}
}
加载SpringBoot上下文
junit5可以不用加@RunWith了
import org.example.springbootdemo.spring.SpringExample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
// junit5
@SpringBootTest // 启动完整 SpringBoot 应用上下文
public class Spring3Test {
@Autowired
private SpringExample springExample;
@Test
public void test1() {
springExample.voidFun();
// 输出:
// hello
}
}
SpringBoot+@MockBean
import org.example.springbootdemo.spring.SpringExample;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.when;
// junit5
@SpringBootTest // 启动完整 SpringBoot 应用上下文
public class Spring3Test {
@MockBean // 替换 Spring 容器中的 springExample bean
private SpringExample springExample;
@Test
public void test1() {
springExample.voidFun();
// 没有任何输出
}
@Test
public void test2() {
when(springExample.doSomething()).thenReturn("hi");
System.out.println(springExample.doSomething());
// 输出:
// hi
}
}
testng
被测试类
import org.springframework.stereotype.Component;
@Component
public class SpringExample {
public void voidFun() {
System.out.println("hello");
}
public String doSomething() {
System.out.println("do something");
return "hello something";
}
}
testng+mockito
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
public class Example7Test {
@Spy
private Example eSpy = new Example();
@BeforeClass // 这个是testng的
public void init() {
// MockitoAnnotations.initMocks(this); // 这种初始化方式已经被废弃了
MockitoAnnotations.openMocks(this); // 作用:将@Spy或者@Mock的对象初始化为spy或者mock对象
}
@Test // 这个是testng的
public void test1() {
doReturn("hi").when(eSpy).doSomething();
System.out.println(eSpy.doSomething());
// 输出:
// hi
}
@Test
public void test2() {
when(eSpy.doSomething()).thenReturn("ni hao");
System.out.println(eSpy.doSomething());
// 输出:
// doSomething (来自doSomething方法的真实调用里打印的日志)
// ni hao
}
}
加载SpringBoot上下文
注意要继承 AbstractTestNGSpringContextTests。
import org.example.springbootdemo.spring.SpringExample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
// testng
@SpringBootTest // 启动完整 SpringBoot 应用上下文
public class Spring5Test extends AbstractTestNGSpringContextTests {
@Autowired // 和junit4 junit5 的使用一样,只是@Test改为testng的
private SpringExample springExample;
@Test
public void test1() {
springExample.voidFun();
// 输出:
// hello
}
}
SpringBoot+@MockBean
@MockBean有点特殊,必须要加@TestExecutionListeners(MockitoTestExecutionListener.class),也不用显示MockitoAnnotations.openMocks(this);
原因暂时还不知道
import org.example.springbootdemo.spring.SpringExample;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
import static org.mockito.Mockito.when;
// testng
@SpringBootTest
// 不加@TestExecutionListeners的话@MockBean的对象是null
// 因为Spring集成TestNG的抽象类: AbstractTestNGSpringContextTests 上并没加关于Mockito相关的测试监听器,所以就没有运用上MockBean的注解功能。
// 官方也有讨论过这个issue,但官方并不打算集成进来,所以叫大家自己加个Listener,因为Mockito这样的产品会有很多种。
@TestExecutionListeners(MockitoTestExecutionListener.class) // 注意要加上这个!!!
public class Spring6Test extends AbstractTestNGSpringContextTests {
@MockBean
private SpringExample springExample;
// @BeforeClass // 这里不需要显示配这个,原因暂时还不知道,在这个测试类中,配这个和不配,没有影响这里的用例结果
// public void init() {
// MockitoAnnotations.openMocks(this);
// }
@Test
public void test1() {
System.out.println(springExample.doSomething());
// 没有任何输出
}
@Test
public void test2() {
when(springExample.doSomething()).thenReturn("hi");
System.out.println(springExample.doSomething());
// 输出:
// hi
}
}
SpringBoot+@InjectMocks+@Mock
import org.example.springbootdemo.spring.SpringExample;
import org.example.springbootdemo.spring.SpringSubExample;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.mockito.Mockito.when;
// testng + @Mock + @InjectMocks
@SpringBootTest
//@TestExecutionListeners(MockitoTestExecutionListener.class) // 这里和 MockitoAnnotations.openMocks() 二选一即可
public class Spring7Test extends AbstractTestNGSpringContextTests {
@InjectMocks
private SpringExample springExample;
@Mock // 将 springSubExample 的mock bean 替换到 springExample bean 中的对应属性bean
private SpringSubExample springSubExample;
@BeforeClass // 这里和 @TestExecutionListeners(MockitoTestExecutionListener.class) 二选一即可
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test1() {
System.out.println(springExample.doSomething());
// 输出真实执行日志
}
@Test
public void test2() {
when(springSubExample.doSub()).thenReturn("hi");
System.out.println(springExample.doSub());
// 输出:
// hi
}
}
spy和mock的区别
被测试类
public class ExampleElse {
public String doSomething() {
System.out.println("doSomething");
return "hello";
}
public void voidFun() {
System.out.println("voidFun");
}
public String doSomethingElse() {
voidFun();
System.out.println("doSomethingElse");
return "good";
}
}
spy
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.mockito.Mockito.doNothing;
public class ExampleElse1Test {
@Spy
private ExampleElse eSpy = new ExampleElse();
@BeforeClass
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test1() {
eSpy.doSomethingElse();
// 输出:
// voidFun
// doSomethingElse
}
@Test
public void test2() {
// 注意doNothing只能对返回void的方法使用。如果是有返回值的方法可以用doReturn().when()h或则when().thenReturn()
doNothing().when(eSpy).voidFun(); // 表示eSpy调用voidFun()方法时,什么都不做,相当于没有调用这个方法
eSpy.doSomethingElse(); // doSomethingElse()这个方法里本来会调用voidFun()方法,有了上面的打桩,这里就跳过调用
// 输出:
// doSomethingElse
}
}
mock
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
public class ExampleElse2Test {
@Mock
private ExampleElse eMock;
@BeforeClass
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test1() {
eMock.doSomethingElse();
// 没有任何输出
// 这是因为 eMock 是一个 mock 对象(由 Mockito 创建的代理对象),
// 并且没有对其方法行为进行明确的 stub(定义模拟行为)。默认情况下,Mockito 创建的 mock
// 对象不会执行任何真实逻辑,也不会输出任何日志,因为 Mockito 会替换真实方法调用为空的 mock 行为。
// 用spy则可以执行真实逻辑,这是二者的区别
}
@Test
public void test2() {
// 通过 Mockito 的 CALLS_REAL_METHODS,让 mock 对象的所有方法调用默认执行真实逻辑。
ExampleElse exampleElseMock = mock(ExampleElse.class, CALLS_REAL_METHODS);
exampleElseMock.doSomethingElse();
// 输出:
// voidFun
// doSomethingElse
}
}
pom依赖
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.4</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.8</version>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.8</version>
<scope>test</scope>
</dependency>