Unit Test _ Mockito Framework

本文详细介绍了Mockito框架在软件测试中的应用,包括为何使用Mock对象、Mockito中的关键方法如mock、when、thenReturn、thenThrow等,以及如何模拟静态方法。同时,讲解了@Mock注解、@BeforeEach和@AfterEach注解的使用,以及Spy方法和@Spy注解的区别。通过实例展示了Mockito在单元测试中的实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unit Test _ Mockito Framework

the process of software test:

1. 为什么要使用 mock

Mock 可以理解为创建一个虚假的对象,或者说模拟出一个对象,在测试环境中用来替换掉真实的对象,以达到我们可以:

  • 验证该对象的某些方法的调用情况,调用了多少次,参数是多少
  • 给这个对象的行为做一个定义,来指定返回结果或者指定特定的动作

2. Mockito 中常用方法

2.1 Mock 方法

mock 方法来自 org.mockito.Mock,它表示可以 mock 一个对象或者是接口。

public static <T> T mock(Class<T> classToMock)
  • classToMock:待 mock 对象的 class 类。
  • 返回 mock 出来的类

实例:使用 mock 方法 mock 一个类

Random random = Mockito.mock(Random.class);

2.2 对 Mock 出来的对象进行行为验证和结果断言

验证是校验待验证的对象是否发生过某些行为,Mockito 中验证的方法是:verify。

verify(mock).someMethod("some arg");
verify(mock, times(1)).someMethod("some arg");

使用 verify 验证:

Verify 配合 time() 方法,可以校验某些操作发生的次数。

@Test
void check() {
    Random random = Mockito.mock(Random.class, "test");
    System.out.println(random.nextInt());
    Mockito.verify(random,Mockito.times(2)).nextInt();
}

断言使用到的类是 Assertions.

Random random = Mockito.mock(Random.class, "test");
Assertions.assertEquals(100, random.nextInt());

输出结果:

org.opentest4j.AssertionFailedError: 
Expected :100
Actual   :0

当使用 mock 对象时,如果不对其行为进行定义,则 mock 对象方法的返回值为返回类型的默认值。

2.3 给 Mock 对象打桩

打桩可以理解为 mock 对象规定一行的行为,使其按照我们的要求来执行具体的操作。在 Mockito 中,常用的打桩方法为

方法含义
when().thenReturn()Mock 对象在触发指定行为后返回指定值
when().thenThrow()Mock 对象在触发指定行为后抛出指定异常
when().doCallRealMethod()Mock 对象在触发指定行为后调用真实的方法

thenReturn() 代码示例

@Test
void check() {
    Random random = Mockito.mock(Random.class, "test");
    Mockito.when(random.nextInt()).thenReturn(100);
    Assertions.assertEquals(100, random.nextInt());
}
测试通过

2.4 Mock 静态方法

首先要引入 Mockito-Inline 的依赖。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.3.1</version>
    <scope>test</scope>
</dependency>

使用 mockStatic() 方法来 mock静态方法的所属类,此方法返回一个具有作用域的模拟对象。

@Test
void range() {
    MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class);
    utilities.when(() -> StaticUtils.range(2, 6)).thenReturn(Arrays.asList(10, 11, 12));
    Assertions.assertTrue(StaticUtils.range(2, 6).contains(10));
}
@Test
void name() {
    MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class);
    utilities.when(StaticUtils::name).thenReturn("bilibili");
    Assertions.assertEquals("1", StaticUtils.	name());
}

执行整个测试类后会报错:

org.mockito.exceptions.base.MockitoException: 
For com.echo.mockito.Util.StaticUtils, static mocking is already registered in the current thread

To create a new mock, the existing static mock registration must be deregistered

原因是因为 mockStatic() 方法是将当前需要 mock 的类注册到本地线程上(ThreadLocal),而这个注册在一次 mock 使用完之后是不会消失的,需要我们手动的去销毁。如过没有销毁,再次 mock 这个类的时候 Mockito 将会提示我们 :”当前对象 mock 的对象已经在线程中注册了,请先撤销注册后再试“。这样做的目的也是为了保证模拟出来的对象之间是相互隔离的,保证同时和连续的测试不会收到上下文的影响。

因此我们修改代码:

class StaticUtilsTest {

    @Test
    void range() {
        try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
            utilities.when(() -> StaticUtils.range(2, 6)).thenReturn(Arrays.asList(10, 11, 12));
            Assertions.assertTrue(StaticUtils.range(2, 6).contains(10));
        }

    }

    @Test
    void name() {
        try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
            utilities.when(StaticUtils::name).thenReturn("bilibili");
            Assertions.assertEquals("bilibili", StaticUtils.name());
        }
    }
}

3. Mockito 中常用注解

3.1 可以代替 Mock 方法的 @Mock 注解

Shorthand for mocks creation - @Mock annotation

Important! This needs to be somewhere in the base class or a test runner:

快速 mock 的方法,使用 @mock 注解。

mock 注解需要搭配 MockitoAnnotations.openMocks(testClass) 方法一起使用。

@Mock
private Random random;

@Test
void check() {
    MockitoAnnotations.openMocks(this);
    Mockito.when(random.nextInt()).thenReturn(100);
    Assertions.assertEquals(100, random.nextInt());
}

3.2 @BeforeEach 与 @BeforeAfter 注解

@Mock
private Random random;

@BeforeEach
void setUp() {
    System.out.println("----测试开始----");
}

@Test
void check() {
    MockitoAnnotations.openMocks(this);
    Mockito.when(random.nextInt()).thenReturn(100);
    Assertions.assertEquals(100, random.nextInt());
}

@AfterEach
void after() {
    System.out.println("----测试结束----");
}

而在 Junit5 中,@Before 和 @After 注解被 @BeforeEach@AfterEach 所替代。

3.3 Spy 方法与 @Spy 注解

spy() 方法与 mock() 方法不同的是

  1. 被 spy 的对象会走真实的方法,而 mock 对象不会
  2. spy() 方法的参数是对象实例,mock 的参数是 class

示例:spy 方法与 Mock 方法的对比

@Test
void check() {
    CheckAuthorityImpl checkAuthority = Mockito.spy(new CheckAuthorityImpl());
    int res = checkAuthority.add(1, 2);
    Assertions.assertEquals(3, res);

    CheckAuthorityImpl checkAuthority1 = Mockito.mock(CheckAuthorityImpl.class);
    int res1 = checkAuthority1.add(1, 2);
    Assertions.assertEquals(3, res1);
 }

输出结果

// 第二个 Assertions 断言失败,因为没有给 checkAuthority1 对象打桩,因此返回默认值
org.opentest4j.AssertionFailedError: 
Expected :3
Actual   :0

使用 @Spy 注解代码示例

@Spy
private CheckAuthorityImpl checkAuthority;

@BeforeEach
void setUp() {
    MockitoAnnotations.openMocks(this);
}

@Test
void check() {
    int res = checkAuthority.add(1, 2);
    Assertions.assertEquals(3, res);
}
### 单元测试中 Mock HttpSession 的解决方案 在 Java 中,当需要对 `HttpSession` 进行单元测试时,通常会使用模拟框架(Mocking Framework),比如 Mockito 或 EasyMock 来创建 `HttpSession` 对象的虚拟实例。以下是通过 Mockito 实现的一个具体例子: #### 使用 Mockito 模拟 HttpSession 下面是一个完整的代码示例,展示如何在单元测试中模拟 `HttpSession` 并验证其行为。 ```java import static org.mockito.Mockito.*; import javax.servlet.http.HttpSession; import org.junit.jupiter.api.Test; public class HttpSessionTest { @Test public void testMockHttpSession() { // 创建一个 HttpSession 的 mock 对象 HttpSession session = mock(HttpSession.class); // 设置属性名和期望的返回值 String attributeName = "user"; Object attributeValue = "testUser"; // 配置 mock 行为 when(session.getAttribute(attributeName)).thenReturn(attributeValue); // 调用 getAttribute 方法并断言结果 Object result = session.getAttribute(attributeName); System.out.println("Retrieved Attribute Value: " + result); // 验证调用了指定的方法一次 verify(session, times(1)).getAttribute(attributeName); } } ``` 上述代码展示了如何利用 Mockito 构建 `HttpSession` 的模拟对象,并设置它的行为以便于测试[^3]。 #### 关键点解析 - **mock**: 用于创建一个给定类型的模拟对象,在这里是 `HttpSession`。 - **when(...).thenReturn(...)`: 定义了当我们调用 `session.getAttribute(String name)` 方法时应该返回什么值。 - **verify**: 确认特定方法是否按预期次数被调用。 对于更复杂的场景,可能还需要模拟其他与 `HttpSession` 相关的行为,例如设置属性 (`setAttribute`) 和移除属性 (`removeAttribute`)。 --- ### 结合 Spring 测试环境下的应用 如果是在基于 Spring 的项目中进行测试,则可以结合 `@WebMvcTest`, `@MockBean` 注解来简化过程: ```java import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import javax.servlet.http.HttpSession; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest public class ControllerWithSessionTest { @Autowired private MockMvc mockMvc; @InjectMocks private YourController controller; @BeforeEach public void setUp() { MockitoAnnotations.openMocks(this); } @Test public void testControllerUsingSession() throws Exception { // 创建一个 MockHttpSession MockHttpSession session = new MockHttpSession(); session.setAttribute("user", "testUser"); // 执行请求并传递 Session this.mockMvc.perform(get("/your-endpoint").session(session)) .andExpect(status().isOk()); } } ``` 在这个案例里,我们使用了 Spring 提供的 `MockHttpSession` 类型作为替代方案[^4]。 --- ### 总结 无论是采用纯 JUnit/Mockito 方式还是集成到 Spring 测试环境中,都可以灵活地实现针对 `HttpSession` 的单元测试需求。这不仅有助于提高代码质量,还能增强系统的可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值