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的上下文
所以就会出现这个错,那么如何解决呢?
image

方法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();, 运行测试
结果还是报错!!!,
image

查找原因,发现异常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("测试设备"));
    }

再次尝试运行,完美解决!
image

posted @ 2026-08-12 10:17  小鱼,  阅读(1)  评论(0)    收藏  举报