mockito

入门教程

复制代码
 /**
     * 
     * https://my.oschina.net/u/4090830/blog/8106620
     */
    @Test
    public void test0() {
        // mock对象
        List mocked = Mockito.mock(List.class);

        // 打桩
        // 设置方法调用并返回值
        // 语法一
        when(mocked.size()).thenReturn(66);
        // 语法二
        doReturn(60).when(mocked).size();
        System.out.println("mock.get result => " + mocked.size());
        // 设置方法调用并抛出异常
        // 语法一
        when(mocked.get(0)).thenThrow(new RuntimeException());
        // 语法二
        Mockito.doThrow(new RuntimeException()).when(mocked).get(Mockito.anyInt());
        // void打桩
        Mockito.doNothing().when(mocked).clear();
        // 为回调做测试桩
        when(mocked.get(1)).thenAnswer(invocation -> {
            List mock = (List) invocation.getMock();
            return "mock.size result => " + mock.size();
        });
        Mockito.doAnswer(invocation -> {
            List mock = (List) invocation.getMock();
            return "mock.size result => " + mock.size();
        }).when(mocked).get(1);
        System.out.println(mocked.get(1));    //mock.size result => 0
        // 对同一方法多次打桩,以最后一次为准
        when(mocked.get(2)).thenReturn("test2_1");
        when(mocked.get(2)).thenReturn("test2_2");
        System.out.println(mocked.get(2));    //test2_2
        System.out.println(mocked.get(2));    //test2_2
        // 设置多次调用同类型结果
        // 语法一
        when(mocked.get(3)).thenReturn("test2_1", "test2_2");
        // 语法二
        when(mocked.get(3)).thenReturn("test2_1").thenReturn("test2_2");
        System.out.println(mocked.get(3));    //第一次调用返回test2_1
        System.out.println(mocked.get(3));    //第二次调用返回test2_2

        // 为连续调用做测试桩(为同一个函数调用的不同的返回值或异常做测试桩)
        when(mocked.get(4)).thenReturn("test2").thenThrow(new RuntimeException());
        doReturn("test2").doThrow(new RuntimeException()).when(mocked).get(4);
        System.out.println(mocked.get(4)); // 第一次调用返回test2
        System.out.println(mocked.get(4)); // 第二次调用抛出异常throw RuntimeException


        // 验证Mock对象的方法是否被调用
        Mockito.verify(mocked).get(Mockito.anyInt());
        Mockito.verify(mocked, Mockito.times(2)).size();

        // 参数匹配器
//        Mockito.verify(mocked).get(Mockito.anyInt()); 表示get(Mockito.anyInt())入参可以是任意int值
        // 常用的参数匹配器
        // 基本类型
        //        Mockito.anyInt()      // 任意int
        //        Mockito.anyLong()     // 任意long
        //        Mockito.anyDouble()   // 任意double
        //        Mockito.anyBoolean()  // 任意boolean
        //
        // 对象类型
        //        Mockito.any()         // 任意对象(包括null)
        //        Mockito.anyString()   // 任意字符串(包括空串和null)
        //        Mockito.anyList()     // 任意List
        //        Mockito.anyMap()      // 任意Map
        //        Mockito.any(Class.class) // 任意指定类型对象
        //
        // 特定值匹配
        //        Mockito.eq(100)       // 等于100
        //        Mockito.isNull()      // 是null
        //        Mockito.isNotNull()   // 不是null
        //        Mockito.contains("abc") // 包含"abc"

        // 验证语法的完整形式 Mockito.verify(mock对象, 验证模式).方法名(参数);
        // 常用验证模式:
        // 1. 默认:至少调用1次
        //        Mockito.verify(mock).someMethod();
        //
        // 2. 精确调用次数
        //        Mockito.verify(mock, Mockito.times(3)).someMethod();  // 恰好3次
        //        Mockito.verify(mock, Mockito.times(0)).someMethod();  // 从未调用
        //
        // 3. 至少调用n次
        //        Mockito.verify(mock, Mockito.atLeast(2)).someMethod();  // 至少2次
        //
        // 4. 最多调用n次
        //        Mockito.verify(mock, Mockito.atMost(1)).someMethod();   // 最多1次
        //
        // 5. 至少1次(明确表达)
        //        Mockito.verify(mock, Mockito.atLeastOnce()).someMethod();
        //
        // 6. 从未调用(明确表达)
        //        Mockito.verify(mock, Mockito.never()).someMethod();
        // 指定方法调用超时时间
//        Mockito.verify(mockList, timeout(100)).get(0);
        // 指定时间内需要完成的次数
//        Mockito.verify(mockList, timeout(200).atLeastOnce()).size();

    }

案例

复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.context.RetryContextSupport;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

/**
 * 飞书通知处理器单元测试
 */
@ExtendWith(MockitoExtension.class)
class FeiShuNotificationHandlerTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private ObjectMapper objectMapper;
    
    @Mock
    private RetryTemplate retryTemplate;
    
    @Mock
    private RRateLimiterService rRateLimiterService;
    
    @Mock
    private RedissonClient redisson;
    
    @Mock
    private RRateLimiter secondRateLimiter;
    
    @Mock
    private RRateLimiter minuteRateLimiter;

    @InjectMocks
    private FeiShuNotificationHandler feiShuNotificationHandler;

    private Map<String, Object> validParams;
    private String testUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/test";
    private String testSecret = "testSecret";
    private String testText = "测试消息";

    @BeforeEach
    void setUp() {
        validParams = new HashMap<>();
        validParams.put("url", testUrl);
        validParams.put("secret", testSecret);
        validParams.put("text", testText);
    }

    /**
     * 测试getNotifyMethod方法
     */
    @Test
    void testGetNotifyMethod() {
        // 执行方法
        NotifyMethodEnum result = feiShuNotificationHandler.getNotifyMethod();
        
        // 验证结果
        assertEquals(NotifyMethodEnum.FEISHU, result);
    }

    /**
     * 测试notify方法成功场景
     */
    @Test
    void testNotifySuccess() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(0);
        feiShuResponse.setMsg("success");
        
        // 模拟RestTemplate和ObjectMapper的行为
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
        when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertTrue(result);
    }

    /**
     * 测试notify方法成功场景 - msg为success
     */
    @Test
    void testNotifySuccessWithMsg() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":1,\"msg\":\"success\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(1);
        feiShuResponse.setMsg("success");
        
        // 模拟RestTemplate和ObjectMapper的行为
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
        when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertTrue(result);
    }

    /**
     * 测试notify方法失败场景 - HTTP状态码非2xx
     */
    @Test
    void testNotifyHttpError() {
        // 模拟RestTemplate返回非2xx状态码
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR));
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertFalse(result);
    }

    /**
     * 测试notify方法失败场景 - 飞书API返回错误
     */
    @Test
    void testNotifyApiError() throws Exception {
        // 准备测试数据
        String errorResponse = "{\"code\":10001,\"msg\":\"参数错误\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(10001);
        feiShuResponse.setMsg("参数错误");
        
        // 模拟RestTemplate和ObjectMapper的行为
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>(errorResponse, HttpStatus.OK));
        when(objectMapper.readValue(eq(errorResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertFalse(result);
    }

    /**
     * 测试notify方法异常场景 - 参数为空
     */
    @Test
    void testNotifyWithEmptyParams() {
        // 准备空参数
        Map<String, Object> emptyParams = new HashMap<>();

        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(emptyParams));
    }

    /**
     * 测试notify方法异常场景 - url为空
     */
    @Test
    void testNotifyWithEmptyUrl() {
        // 准备缺少url的参数
        Map<String, Object> paramsWithoutUrl = new HashMap<>();
        paramsWithoutUrl.put("secret", testSecret);
        paramsWithoutUrl.put("text", testText);
        
        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutUrl));
    }

    /**
     * 测试notify方法异常场景 - secret为空
     */
    @Test
    void testNotifyWithEmptySecret() {
        // 准备缺少secret的参数
        Map<String, Object> paramsWithoutSecret = new HashMap<>();
        paramsWithoutSecret.put("url", testUrl);
        paramsWithoutSecret.put("text", testText);
        
        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutSecret));
    }

    /**
     * 测试notify方法异常场景 - text为空
     */
    @Test
    void testNotifyWithEmptyText() {
        // 准备缺少text的参数
        Map<String, Object> paramsWithoutText = new HashMap<>();
        paramsWithoutText.put("url", testUrl);
        paramsWithoutText.put("secret", testSecret);
        
        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutText));
    }

    /**
     * 测试notify方法异常场景 - RestTemplate抛出异常
     */
    @Test
    void testNotifyWithRestTemplateException() {
        // 模拟RestTemplate抛出异常
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenThrow(new AlarmException(CommonResultEnum.REMOTE_FAIL));


        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(validParams));
    }

    /**
     * 测试notify方法异常场景 - ObjectMapper抛出异常
     */
    @Test
    void testNotifyWithObjectMapperException() throws Exception {
        // 模拟RestTemplate正常返回,但ObjectMapper解析异常
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>("Invalid JSON", HttpStatus.OK));
        when(objectMapper.readValue(eq("Invalid JSON"), eq(FeiShuResponse.class)))
                .thenThrow(new RuntimeException("JSON parse error"));

        // 执行方法并验证异常
        assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(validParams));
    }

    /**
     * 测试makeSign方法
     */
    @Test
    void testMakeSign() throws Exception {
        // 使用反射调用私有方法
        String sign = (String) ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "makeSign", testSecret, 1638360000L);
        
        // 验证签名不为空
        assertNotNull(sign);
        assertFalse(sign.isEmpty());
        
        // 验证相同输入产生相同签名
        String sign2 = (String) ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "makeSign", testSecret, 1638360000L);
        assertEquals(sign, sign2);
        
        // 验证不同输入产生不同签名
        String sign3 = (String) ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "makeSign", "differentSecret", 1638360000L);
        assertNotEquals(sign, sign3);
        
        String sign4 = (String) ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "makeSign", testSecret, 1638360001L);
        assertNotEquals(sign, sign4);
    }

    /**
     * 测试FeiShuResponse的isSuccess方法
     */
    @Test
    void testFeiShuResponseIsSuccess() {
        // 测试code为0的情况
        FeiShuResponse response1 = new FeiShuResponse();
        response1.setCode(0);
        assertTrue(response1.isSuccess());
        
        // 测试msg为success的情况
        FeiShuResponse response2 = new FeiShuResponse();
        response2.setMsg("success");
        assertTrue(response2.isSuccess());
        
        // 测试code为0且msg为success的情况
        FeiShuResponse response3 = new FeiShuResponse();
        response3.setCode(0);
        response3.setMsg("success");
        assertTrue(response3.isSuccess());
        
        // 测试code不为0且msg不为success的情况
        FeiShuResponse response4 = new FeiShuResponse();
        response4.setCode(1);
        response4.setMsg("error");
        assertFalse(response4.isSuccess());
        
        // 测试code为null但msg为success的情况
        FeiShuResponse response5 = new FeiShuResponse();
        response5.setCode(null);
        response5.setMsg("success");
        assertTrue(response5.isSuccess());
        
        // 测试code为0但msg为null的情况
        FeiShuResponse response6 = new FeiShuResponse();
        response6.setCode(0);
        response6.setMsg(null);
        assertTrue(response6.isSuccess());
    }
    
    /**
     * 测试重试机制 - 第一次尝试成功
     */
    @Test
    void testRetryMechanismFirstAttemptSuccess() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(0);
        feiShuResponse.setMsg("success");
        
        // 模拟RestTemplate的行为
        ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(responseEntity);
        
        // 模拟ObjectMapper的行为
        when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 模拟RetryTemplate的行为
        when(retryTemplate.execute(any())).thenAnswer(invocation -> {
            // 获取回调函数
            RetryCallback<ResponseEntity<String>, Exception> callback =
                    invocation.getArgument(0);
            
            // 创建模拟的RetryContext,传入parent上下文而不是null
            RetryContext parentContext = new RetryContextSupport(null);
            RetryContext context = new RetryContextSupport(parentContext);
            
            // 执行回调,模拟第一次尝试成功
            try {
                return callback.doWithRetry(context);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertTrue(result);
        
        // 验证RestTemplate只被调用了一次
        // 注意:由于使用了RetryTemplate,我们无法直接验证调用次数,但可以通过日志或其他方式验证
    }
    
    /**
     * 测试重试机制 - 第二次尝试成功
     */
    @Test
    void testRetryMechanismSecondAttemptSuccess() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(0);
        feiShuResponse.setMsg("success");
        
        // 记录RestTemplate调用次数
        final int[] restTemplateCallCount = {0};
        
        // 模拟RestTemplate的行为 - 第一次调用失败,第二次调用成功
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class))).thenAnswer(invocation -> {
                    restTemplateCallCount[0]++;
                    if (restTemplateCallCount[0] == 1) {
                        // 第一次调用失败
                        System.out.println("First attempt failed");
                        throw new RuntimeException("First attempt failed");
                    } else {
                        // 第二次调用成功
                        System.out.println("Second attempt success");
                        return new ResponseEntity<>(successResponse, HttpStatus.OK);
                    }
                });
        
        when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 模拟RetryTemplate的行为 - 执行重试逻辑
        when(retryTemplate.execute(any())).thenAnswer(invocation -> {
            // 获取回调函数
            RetryCallback<ResponseEntity<String>, Exception> callback = invocation.getArgument(0);
            
            // 创建模拟的RetryContext
            RetryContext parentContext = new RetryContextSupport(null);
            RetryContext context = new RetryContextSupport(parentContext);
            
            try {
                // 第一次尝试
                System.out.println("First attempt");
                return callback.doWithRetry(context);
            } catch (Exception e) {
                // 第一次失败,进行重试
                try {
                    // 第二次尝试
                    System.out.println("Second attempt");
                    return callback.doWithRetry(context);
                } catch (Exception retryException) {
                    // 重试也失败,抛出异常
                    throw new RuntimeException(retryException);
                }
            }
        });
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertTrue(result);
        
        // 验证RestTemplate被调用了两次
        assertEquals(2, restTemplateCallCount[0]);
    }
    
    /**
     * 测试initRateLimiters方法
     */
    @Test
    void testInitRateLimiters() {
        // 使用反射调用私有方法
        ReflectionTestUtils.invokeMethod(feiShuNotificationHandler, "initRateLimiters");
        
        // 验证RRateLimiterService的setRRateLimiter方法被正确调用
        // 秒级限流器:5次/秒
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
        // 分钟级限流器:100次/分钟
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
    }
    
    /**
     * 测试acquireRateLimitPermits方法 - 两个限流器都成功获取令牌
     */
    @Test
    void testAcquireRateLimitPermitsSuccess() {
        // 模拟限流器服务返回true
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
        
        // 使用反射调用私有方法
        Boolean result = ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "acquireRateLimitPermits");
        
        // 验证结果
        assertTrue(result);
        
        // 验证限流器服务被正确调用
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
    }
    
    /**
     * 测试acquireRateLimitPermits方法 - 秒级限流器失败
     */
    @Test
    void testAcquireRateLimitPermitsSecondLimiterFail() {
        // 模拟秒级限流器失败,分钟级限流器成功
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(false);

        // 使用反射调用私有方法
        Boolean result = ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "acquireRateLimitPermits");
        
        // 验证结果
        assertFalse(result);
        
        // 验证秒级限流器被调用
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        // 分钟级限流器不应该被调用,因为秒级已经失败
        org.mockito.Mockito.verify(rRateLimiterService, org.mockito.Mockito.never()).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
    }
    
    /**
     * 测试acquireRateLimitPermits方法 - 分钟级限流器失败
     */
    @Test
    void testAcquireRateLimitPermitsMinuteLimiterFail() {
        // 模拟秒级限流器成功,分钟级限流器失败
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(false);
        
        // 使用反射调用私有方法
        Boolean result = (Boolean) ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "acquireRateLimitPermits");
        
        // 验证结果
        assertFalse(result);
        
        // 验证两个限流器都被调用
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
    }
    
    /**
     * 测试waitForRateLimitPermits方法
     */
    @Test
    void testWaitForRateLimitPermits() {
        // 模拟Redisson客户端返回RRateLimiter
        when(redisson.getRateLimiter("feishu:rate:limiter:second")).thenReturn(secondRateLimiter);
        when(redisson.getRateLimiter("feishu:rate:limiter:minute")).thenReturn(minuteRateLimiter);
        
        // 使用反射调用私有方法
        ReflectionTestUtils.invokeMethod(feiShuNotificationHandler, "waitForRateLimitPermits");
        
        // 验证Redisson客户端被正确调用
        org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:second");
        org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:minute");
        
        // 验证RRateLimiter的acquire方法被调用
        org.mockito.Mockito.verify(secondRateLimiter).acquire();
        org.mockito.Mockito.verify(minuteRateLimiter).acquire();
    }
    
    /**
     * 测试doNotify方法 - 限流器成功获取令牌
     */
    @Test
    void testDoNotifyRateLimitSuccess() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(new HashMap<>());
        
        // 模拟限流器服务返回true,表示成功获取令牌
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
        
        // 模拟RetryTemplate的行为
        when(retryTemplate.execute(any())).thenReturn(responseEntity);
        
        // 使用反射调用私有方法
        ResponseEntity<String> result = ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "doNotify", testUrl, testSecret, testText, requestEntity);
        
        // 验证结果
        assertEquals(HttpStatus.OK, result.getStatusCode());
        assertEquals(successResponse, result.getBody());
        
        // 验证限流器服务被调用
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
    }
    
    /**
     * 测试doNotify方法 - 限流器失败后等待获取令牌
     */
    @Test
    void testDoNotifyRateLimitWaitAndSuccess() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(new HashMap<>());
        
        // 模拟限流器服务返回false,表示获取令牌失败
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(false);
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
        
        // 模拟Redisson客户端返回RRateLimiter
        when(redisson.getRateLimiter("feishu:rate:limiter:second")).thenReturn(secondRateLimiter);
        when(redisson.getRateLimiter("feishu:rate:limiter:minute")).thenReturn(minuteRateLimiter);
        
        // 模拟RetryTemplate的行为
        when(retryTemplate.execute(any())).thenReturn(responseEntity);
        
        // 使用反射调用私有方法
        ResponseEntity<String> result = ReflectionTestUtils.invokeMethod(
                feiShuNotificationHandler, "doNotify", testUrl, testSecret, testText, requestEntity);
        
        // 验证结果
        assertEquals(HttpStatus.OK, result.getStatusCode());
        assertEquals(successResponse, result.getBody());
        
        // 验证限流器服务被调用
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        
        // 验证Redisson客户端被调用
        org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:second");
        org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:minute");
        
        // 验证RRateLimiter的acquire方法被调用
        org.mockito.Mockito.verify(secondRateLimiter).acquire();
        org.mockito.Mockito.verify(minuteRateLimiter).acquire();
    }
    
    /**
     * 测试notify方法 - 包含限流功能
     */
    @Test
    void testNotifyWithRateLimit() throws Exception {
        // 准备测试数据
        String successResponse = "{\"code\":0,\"msg\":\"success\"}";
        FeiShuResponse feiShuResponse = new FeiShuResponse();
        feiShuResponse.setCode(0);
        feiShuResponse.setMsg("success");
        
        // 模拟限流器服务返回true,表示成功获取令牌
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
        when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
        
        // 模拟RestTemplate和ObjectMapper的行为
        when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
                .thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
        when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
                .thenReturn(feiShuResponse);
        
        // 模拟RetryTemplate的行为
        when(retryTemplate.execute(any())).thenAnswer(invocation -> {
            // 获取回调函数
            RetryCallback<ResponseEntity<String>, Exception> callback =
                    invocation.getArgument(0);
            
            // 创建模拟的RetryContext
            RetryContext parentContext = new RetryContextSupport(null);
            RetryContext context = new RetryContextSupport(parentContext);
            
            // 执行回调,模拟第一次尝试成功
            try {
                return callback.doWithRetry(context);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        
        // 执行方法
        boolean result = feiShuNotificationHandler.notify(validParams);
        
        // 验证结果
        assertTrue(result);
        
        // 验证限流器服务被调用
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
        org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
        org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
    }
}
相关推荐
小范馆19 分钟前
解决 Windows 11 安装时提示 “不支持 TPM 2.0” 和 “不支持安全启动” 的问题
windows·安全
还下着雨ZG23 分钟前
【Window技能 01】每天自动关机:使用CMD脚本+任务计划程序实现每天定时关闭计算机
windows
洛水如云43 分钟前
重塑数据管理逻辑!文件夹同步的实用指南(含工具选型 + 实操步骤)
windows·microsoft·电脑
石像鬼₧魂石1 小时前
windows系统139/tcp与445/tcp端口渗透完整流程闭环(复习总结)
windows·网络协议·tcp/ip
BD_Marathon2 小时前
MyBatis——封装SqlSessionUtils工具类并测试功能
java·windows·mybatis
洛水如云3 小时前
笔记本数据迁移新电脑:从备份到实操的高效指南(适用于台式机)
windows·microsoft·电脑
vortex54 小时前
Windows SeBackupPrivilege 与 SeRestorePrivilege 特权利用
windows
佑白雪乐4 小时前
<Python基础第2集>速通list+tuple+string+序列+set+dict容器
windows·python·list
悾说5 小时前
xRDP实现Linux图形化通过Windows RDP访问Linux远程桌面
linux·运维·windows
LeetCode天天刷5 小时前
1348 推文计数【区间】
java·服务器·windows