使用MockMvc来代替RestTemplate对Controller进行单元测试

本文探讨了在Java Spring框架下,使用MockMvc和RestTemplate进行Controller单元测试的区别。重点在于事务控制的有效性,指出MockMvc能有效利用@Transactional进行事务回滚,而RestTemplate则无法做到。

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

对Controller进行单元测试可以通过RestTemplat发送一个http请求来实现。也可以通过MockMvc来实现,二者还是有很大区别的,参考Difference between MockMvc and RestTemplate in integration tests

简单来说,二者的直接区别是:

使用MockMvc,通常是设置一个完整的Web应用程序上下文,来模拟HTTP请求和响应,它创建的是一个假的DispatcherServlet来模拟MVC堆栈的运行方式,没有真正的网络连接。

使用RestTemplate,部署的是一个实际的Web服务器来监听您发送的HTTP请求,并响应结果。

但是二者还有个非常重要的差异:

使用MockMvc可以对Controller进行事务控制,即@Transactional在MockMvc的单元测试中是有效的。但是使用RestTemplate创建网络连接的测试,无法对Controller进行事务控制,即@Transactional在RestTemplate的单元测试中是无效的。

以下是代码演示:

原始的Controller:

@RestController
@RequestMapping("card")
public class CardController {

  private final CardCollectService cardCollectService;

  @PostMapping("collects")
  public HyCardCollect createCollect(@RequestBody HyCardCollect cardCollect) {
    return cardCollectService.createHyCardCollect(cardCollect);
  }

}

使用RestTemplate对Controller做单元测试: 

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Transactional
@Slf4j
public class CardControllerRestTest {

  @Autowired
  private TestRestTemplate testRestTemplate;

  @Test
  public void testCardCollect() {
    ResponseEntity<PageInfo<HyCardSearchResult>> res = Utils
        .getRootLoginTemplate(testRestTemplate)
        .exchange("/card/collects", HttpMethod.POST,
            new HttpEntity<>(ImmutableMap.of("cardId", 20, "userId", 1, "companyId", "123")),
            new ParameterizedTypeReference<PageInfo<HyCardSearchResult>>() {
            });
    log.debug("resp status={}", res.getStatusCode());
    Assert.assertEquals(res.getStatusCode(), HttpStatus.OK);
  }
}

也可以使用MockMvc对Controller做单元测试,下面是个标准的范例:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@Slf4j
public class CardControllerMockmvcTest {

  @Autowired
  private WebApplicationContext context;
  private MockMvc mvc;

  @Before
  public void setUp() throws Exception {
    mvc = MockMvcBuilders.webAppContextSetup(context).build();//建议使ç¨è¿ç§
  }

  @Test
  public void testCardCollect() throws Exception {
    HyCardCollect collect = new HyCardCollect();
    collect.setCardId(20);
    collect.setUserId(1);
    collect.setCompanyId("123");

    MockHttpServletResponse response = mvc.perform(MockMvcRequestBuilders.post("/card/collects")
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content(JSON.toJSONString(collect)))
        .andDo(MockMvcResultHandlers.print())
        .andReturn()
        .getResponse();
    Assert.assertEquals(HttpStatus.OK.value(), response.getStatus());
  }
}

执行CardControllerRestTest,运行结果:

可以看出,它实际上插入了数据。

删除这条数据,再执行CardControllerMockmvcTest,运行结果:

可以看出它并没有插入数据!

尽管两个测试类都添加了@Transactional对事务进行回滚,但是使用RestTemplate的测试类,这个注解实际上是无效的。

所以:如果你的Controller单元测试对事务有要求,请使用MockMvc而不是RestTemplate。

<think>好的,用户想用WireMock-JRE8在Spring Boot项目中模拟Controller的HTTP接口。我需要先回忆一下WireMock的基本用法,以及如何集成到Spring Boot中。 首先,WireMock是一个用于模拟HTTP服务的工具,可以方便地模拟外部依赖的API。用户提到的是WireMock-JRE8,可能是指支持Java 8的版本,或者某个特定的库。需要确认依赖是否正确。 接下来,用户可能已经知道如何在Spring Boot中配置Controller,但现在需要用WireMock代替实际的Controller响应。通常,使用WireMock需要启动一个模拟服务器,并定义stub来指定请求和响应。 然后,我需要考虑如何将WireMock集成到Spring Boot测试中。可能需要添加相关的依赖,比如wiremock-jre8的库,然后在测试类中配置WireMock服务器。Spring Boot Test的注解,比如@SpringBootTest,可能会用到,同时需要确保测试时WireMock服务器已经启动。 另外,可能需要设置WireMock的端口,避免与应用程序的端口冲突。通常,WireMock默认使用8080端口,但如果在测试中需要不同的端口,可以通过代码配置。或者使用随机端口,动态获取后传递给测试用例。 然后,考虑如何编写测试用例。用户可能需要使用RestTemplate或者TestRestTemplate来发送请求到WireMock的模拟接口,并验证响应是否符合预期。这时候需要定义stub,比如当某个URL被访问时,返回特定的状态码和响应体。 还需要注意,如果应用程序本身需要调用外部服务,而用户想用WireMock来模拟这些外部服务,这时候可能需要配置应用程序的HTTP客户端,比如使用RestTemplate,将其指向WireMock的地址。但在当前问题中,用户是要模拟自己的Controller接口,可能意味着在测试中覆盖实际的Controller,或者通过某种方式让WireMock拦截请求到特定路径。 不过,可能更常见的做法是用WireMock来模拟外部服务,而不是自己的Controller。如果用户确实需要模拟自己的Controller,可能需要结合SpringMockMvc,但WireMock更适合模拟外部HTTP服务。这时候可能需要澄清用户的需求,但根据问题描述,用户可能希望模拟一个Controller的接口,比如在集成测试中替换某个依赖的Controller,或者进行契约测试。 可能的步骤包括:添加WireMock依赖,编写测试类,启动WireMock服务器,配置stub,发送请求并断言响应。同时,需要确保在测试中WireMock服务器正确启动,并且应用程序配置正确,可能涉及动态端口的处理。 另外,可能需要使用@DynamicPropertySource来动态设置属性,比如将应用程序中调用的外部服务URL指向WireMock的地址。不过,如果是模拟自己的Controller,可能需要不同的方法,或者用户可能有其他意图。 需要检查用户提供的引用内容,引用2提到了Controller注入Service,引用3提到了多环境配置,可能用户的项目结构是标准的Spring Boot,现在需要在测试中模拟某个Controller的HTTP响应,比如在测试其他组件时,避免调用实际的Controller,而是用WireMock返回预设的响应。 综上,步骤可能包括: 1. 添加WireMock-JRE8依赖到项目的pom.xml或build.gradle。 2. 在测试类中启动WireMock服务器。 3. 配置WireMock的stub,定义请求匹配条件和响应内容。 4. 在测试中发送HTTP请求到WireMock的地址,验证响应是否正确。 5. 处理端口和URL的动态配置,确保应用或测试客户端连接到WireMock。 可能的代码示例包括使用@Rule或手动启动WireMockServer实例,配置stub,然后使用TestRestTemplate发送请求。同时,需要处理可能的端口冲突,比如使用动态端口,并在stub配置中指定正确的URL路径。 另外,可能需要考虑Spring Boot测试上下文,使用@SpringBootTest(webEnvironment = ...)来启动适当的Web环境。如果WireMock作为外部服务模拟,可能需要将应用程序的某些属性指向WireMock的地址,例如使用@DynamicPropertySource设置属性。 最后,生成相关问题,可能涉及WireMock的高级用法、如何验证请求次数、如何处理不同的HTTP方法等。</think>在Spring Boot项目使用WireMock-JRE8模拟Controller接口的HTTP接口,可通过以下步骤实现: --- ### **1. 添加依赖** 在`pom.xml`中添加WireMock-JRE8依赖: ```xml <dependency> <groupId>org.wiremock</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency> ``` --- ### **2. 编写测试类** ```java @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WireMockControllerTest { @LocalServerPort private int appPort; // Spring Boot应用的随机端口 private WireMockServer wireMockServer; @BeforeEach void setup() { // 启动WireMock服务器,使用随机端口避免冲突 wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort()); wireMockServer.start(); // 配置WireMock的Stub(模拟接口) wireMockServer.stubFor( WireMock.get("/mock/api") // 匹配GET请求路径 .willReturn( WireMock.aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody("{ \"message\": \"Mocked Response\" }") ) ); // 动态设置应用调用WireMock的地址(假设应用需要调用此接口) System.setProperty("external.service.url", "http://localhost:" + wireMockServer.port()); } @Test void testMockedController() { // 使用TestRestTemplate发送请求到WireMock接口 TestRestTemplate restTemplate = new TestRestTemplate(); ResponseEntity<String> response = restTemplate.getForEntity( "http://localhost:" + wireMockServer.port() + "/mock/api", String.class ); // 验证响应 assertEquals(200, response.getStatusCodeValue()); assertTrue(response.getBody().contains("Mocked Response")); } @AfterEach void tearDown() { wireMockServer.stop(); } } ``` --- ### **3. 关键配置说明** 1. **动态端口**:通过`dynamicPort()`避免端口冲突[^3]。 2. **Stub定义**:使用`wireMockServer.stubFor()`定义接口的请求匹配条件和响应。 3. **动态配置**:若应用需要调用模拟接口,通过`System.setProperty()`动态注入WireMock地址[^2]。 --- ### **4. 验证请求细节(可选)** ```java // 验证WireMock是否收到请求 wireMockServer.verify( WireMock.getRequestedFor(WireMock.urlEqualTo("/mock/api")) .withHeader("Accept", WireMock.equalTo("application/json")) ); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alphathur

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值