Satoken环境下进行WebMvcTest单元测试报错SaTokenContext 上下文尚未初始化
先上代码
之前写了一个单元测试,跑着一直没问题,最近集成了Satoken以后,单元测试就无法通过了
@WebMvcTest(DeviceController.class)
class DeviceControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private IDeviceService deviceService;
@Autowired
private ObjectMapper objectMapper;
private Device device;
@BeforeEach
void setUp() {
device = new Device();
device.setId(1L);
device.setBizId(1L);
device.setDeviceName("测试设备");
device.setDeviceCode("TEST001");
device.setManufacturer("PHILIPS");
device.setModelNumber("MX800");
device.setMonitorFlag("1");
device.setMonitorType("单机");
device.setConnMode("client");
device.setBizDomain("DEFAULT");
}
@Test
void testList() throws Exception {
List<Device> deviceList = new ArrayList<>();
deviceList.add(device);
Page<Device> page = new Page<>(1, 10);
page.setRecords(deviceList);
page.setTotal(1);
Mockito.when(deviceService.getPage(ArgumentMatchers.any(), ArgumentMatchers.any(Device.class)))
.thenReturn(page);
mockMvc.perform(get("/api/v1/device/list")
.param("page", "1")
.param("size", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(ResultCode.SUCCESS.getCode()))
.andExpect(jsonPath("$.msg").value("查询成功"))
.andExpect(jsonPath("$.total").value(1))
.andExpect(jsonPath("$.rows[0].deviceName").value("测试设备"));
}
以下是报错信息,由于我们是WebMvcTest,按需加载,未启动web容器环境,也没有加载SaToken的上下文
所以就会出现这个错,那么如何解决呢?

方法1(不推荐)
使用@SpringBootTest加载完整的web上下文,然后模拟登录
这样是可以跑通了,但是由于启动了完整的web容器,会导致单元测试运行效率急剧降低
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
方法2 手动注入上下文
既然报错是SaTokenContext 上下文尚未初始化,那我手动注入一个上下文是不是就可以了?
翻了下SaToken源码,发现官方提供了原生的api
public class SaTokenContextMockUtil {
/**
* 写入 Mock 上下文
*/
public static void setMockContext() {
SaRequestForMock request = new SaRequestForMock();
SaResponseForMock response = new SaResponseForMock();
SaStorageForMock storage = new SaStorageForMock();
SaManager.getSaTokenContext().setContext(request, response, storage);
}
/**
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
*
* @param fun /
*/
public static void setMockContext(SaFunction fun) {
try {
setMockContext();
fun.run();
} finally {
clearContext();
}
}
/**
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
*
* @param fun /
*/
public static <T> T setMockContext(SaRetGenericFunction<T> fun) {
try {
setMockContext();
return fun.run();
} finally {
clearContext();
}
}
/**
* 清除上下文
*/
public static void clearContext() {
SaManager.getSaTokenContext().clearContext();
}
}
直接调用SaTokenContextMockUtil.setMockContext();, 运行测试
结果还是报错!!!,

查找原因,发现异常cn.dev33.satoken.exception.NotImplException是在SaStrategy.routeMatcher抛出的
/**
* 路由匹配策略
*/
public SaRouteMatchFunction routeMatcher = (pattern, path) -> {
throw new NotImplException("未实现具体路由匹配策略").setCode(SaErrorCode.CODE_12401);
};
原来是手动注入的mock测试上下文中没有具体的路由匹配策略实现,所以抛出了异常,
satoken也添加了注释说明
/**
* Sa-Token 策略对象
* <p>
* 此类统一定义框架内的一些关键性逻辑算法,方便开发者进行按需重写,例:
* </p>
* <pre>
// SaStrategy全局单例,所有方法都用以下形式重写
SaStrategy.instance.setCreateToken((loginId, loginType) -》 {
// 自定义Token生成的算法
return "xxxx";
});
* </pre>
*
* @author click33
* @since 1.27.0
*/
public final class SaStrategy
可以看到,注释中说明这里的类是全局单例的,那我们手动构造一个routerMatch即可
SaStrategy.instance.routeMatcher = (pattern, path) -> true;
完整代码
void testList() throws Exception {
// 手动构造satoken上下文
SaTokenContextMockUtil.setMockContext();
// 模拟登录
StpUtil.login(1L);
// 获取token value,供后面mock使用
String tokenValue = StpUtil.getTokenValue();
// 手动实现一个路由匹配策略
SaStrategy.instance.routeMatcher = (pattern, path) -> true;
List<Device> deviceList = new ArrayList<>();
deviceList.add(device);
Page<Device> page = new Page<>(1, 10);
page.setRecords(deviceList);
page.setTotal(1);
Mockito.when(deviceService.getPage(ArgumentMatchers.any(), ArgumentMatchers.any(Device.class)))
.thenReturn(page);
mockMvc.perform(get("/api/v1/device/list")
// 关键!! 将模拟登录获取的token传递至cookie,否则会报401!
.cookie(new Cookie("satoken", tokenValue))
.param("page", "1")
.param("size", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(ResultCode.SUCCESS.getCode()))
.andExpect(jsonPath("$.msg").value("查询成功"))
.andExpect(jsonPath("$.total").value(1))
.andExpect(jsonPath("$.rows[0].deviceName").value("测试设备"));
}
再次尝试运行,完美解决!


浙公网安备 33010602011771号