Spring Boot微服务架构下,控制器作为对外提供接口的第一层,其测试需要覆盖接口逻辑、参数校验、权限校验等多个维度。但在实际测试中,跨服务调用和JWT认证是两个常见的阻碍点,前者会让测试依赖外部服务状态,后者要求请求必须携带合法令牌,否则无法通过权限校验。

跨服务调用的测试处理方案
微服务中控制器经常会调用其他服务的接口获取数据,比如订单服务调用用户服务查询用户信息。如果测试时发起真实的跨服务调用,一旦用户服务不可用或者返回数据不符合预期,就会导致控制器测试失败,而且真实调用还会增加测试执行时间。因此我们需要使用Mock技术模拟跨服务调用的返回结果。
使用MockBean模拟Feign客户端调用
如果项目中使用Feign作为跨服务调用的客户端,只需要在测试类中用@MockBean注解标记Feign客户端,然后定义其方法的返回结果即可。以下是示例代码:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.mockito.Mockito.when;
// 指定要测试的控制器类
@WebMvcTest(OrderController.class)
public class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
// 模拟Feign客户端,避免真实跨服务调用
@MockBean
private UserFeignClient userFeignClient;
@Test
public void testGetOrderWithUserInfo() throws Exception {
// 模拟跨服务调用返回的用户信息
UserInfo mockUserInfo = new UserInfo(1L, "测试用户", "13800138000");
when(userFeignClient.getUserById(1L)).thenReturn(mockUserInfo);
// 发起接口请求,验证返回状态
mockMvc.perform(get("/orders/1"))
.andExpect(status().isOk());
}
}
使用WireMock模拟HTTP接口调用
如果是通过RestTemplate等工具直接发起HTTP跨服务调用,可以使用WireMock在本地启动一个模拟服务,返回预设的响应数据。首先需要引入WireMock依赖,然后配置模拟接口的响应:
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
public class ProductControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
private WireMockServer wireMockServer;
@BeforeEach
public void setup() {
// 启动WireMock服务,端口设置为8081
wireMockServer = new WireMockServer(8081);
wireMockServer.start();
WireMock.configureFor("localhost", 8081);
// 模拟库存服务的查询接口返回
WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/inventory/1"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{"productId":1,"stock":100}")));
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@AfterEach
public void teardown() {
wireMockServer.stop();
}
@Test
public void testGetProductWithStock() throws Exception {
mockMvc.perform(get("/products/1"))
.andExpect(status().isOk());
}
}
JWT认证的测试处理方案
微服务中通常会通过JWT令牌做接口权限校验,控制器层的拦截器或者过滤器会校验请求头中的Authorization字段,如果令牌不合法或者过期,就会直接返回401错误。因此测试时需要构造合法的JWT令牌添加到请求头中。
生成测试用JWT令牌
我们可以编写一个工具类生成测试专用的JWT令牌,令牌的签名密钥和过期时间可以和正式环境保持一致,但是可以简化载荷内容,只保留必要的用户标识和权限信息:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTestUtil {
// 和项目配置一致的JWT签名密钥
private static final String SECRET_KEY = "test-secret-key-123456";
// 令牌过期时间,测试场景可以设置较长
private static final long EXPIRATION_TIME = 3600000;
public static String generateTestToken(Long userId, String role) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("role", role);
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
在测试请求中添加JWT令牌
生成令牌之后,只需要在MockMvc发起请求时,通过header方法添加Authorization请求头即可,格式为Bearer 空格加令牌内容:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.mockito.Mockito.when;
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
public void testGetUserInfoWithJwt() throws Exception {
// 生成测试用JWT令牌,用户ID为1,角色为普通用户
String token = JwtTestUtil.generateTestToken(1L, "ROLE_USER");
when(userService.getUserById(1L)).thenReturn(new UserInfo(1L, "测试用户"));
// 添加Authorization请求头,携带JWT令牌
mockMvc.perform(get("/users/1")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
@Test
public void testGetUserInfoWithoutJwt() throws Exception {
// 不携带令牌,预期返回401未授权
mockMvc.perform(get("/users/1"))
.andExpect(status().isUnauthorized());
}
}
综合测试场景示例
实际测试中经常会同时遇到跨服务调用和JWT认证的场景,我们可以把两种处理方式结合起来,既模拟跨服务调用的返回,又添加合法的JWT令牌到请求中:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.mockito.Mockito.when;
@WebMvcTest(OrderController.class)
public class OrderControllerIntegratedTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserFeignClient userFeignClient;
@MockBean
private ProductFeignClient productFeignClient;
@Test
public void testCreateOrder() throws Exception {
// 生成管理员角色的JWT令牌
String token = JwtTestUtil.generateTestToken(1L, "ROLE_ADMIN");
// 模拟用户服务返回
when(userFeignClient.getUserById(1L)).thenReturn(new UserInfo(1L, "管理员"));
// 模拟商品服务返回
when(productFeignClient.getProductById(1L)).thenReturn(new ProductInfo(1L, "测试商品", 100));
// 发起创建订单请求,携带令牌和模拟的跨服务调用
mockMvc.perform(post("/orders")
.header("Authorization", "Bearer " + token)
.param("userId", "1")
.param("productId", "1")
.param("count", "2"))
.andExpect(status().isOk());
}
}
注意事项
- Mock的跨服务调用返回数据需要和真实接口的返回结构保持一致,否则控制器处理时可能会出现反序列化错误。
- 测试用的JWT令牌密钥需要和项目正式配置的密钥一致,否则会被校验为非法令牌。
- 如果项目中使用了自定义的JWT校验逻辑,需要确认测试环境是否加载了对应的校验配置,避免出现校验逻辑不生效的情况。
- 测试完成后不需要清理JWT令牌,因为令牌是客户端生成的,不会在服务端存储状态。
Spring_Boot微服务控制器测试JWT认证跨服务调用修改时间:2026-07-17 21:48:54