使用注释式 Resilience4j 测试 SpringBoot

2024-05-03

我正在使用注释式 Resilience4j春季启动应用程序 https://github.com/SidekickJohn/demo称为“演示”。通过 RestTemplate 调用外部后端时,我想使用 TimeLimiter 和 Retry 来实现以下目标:

  1. 将 REST 调用持续时间限制为 5 秒 --> 如果需要更长的时间,则会失败并出现 TimeoutException
  2. 重试 TimeoutException --> 最多尝试 2 次

为了查看我的弹性设置的配置是否按设计工作,我编写了一个集成测试。此测试在配置文件“test”下运行,并使用“application-test.yml”进行配置:

  1. 使用 TestRestTemplate 向我的“SimpleRestEndpointController”发送调用
  2. 控制器调用我的业务服务“CallExternalService”,它有一个带注释的方法“getPersonById”(注释:@TimeLimiter、@Retry)
  3. 从此方法中,使用模拟的 RestTemplate 来调用“FANCY_URL”处的外部后端
  4. 使用 Mockito,RestTemplate 对外部后端的调用会减慢(使用 Thread.sleep)
  5. 我希望 TimeLimiter 在 5 秒后取消调用,并且 Retry 确保再次尝试 RestTemplate 调用(验证 RestTemplate 已被调用两次)

PROBLEM:TimeLimiter 和 Retry 已注册,但不执行其工作(TimeLimiter 不限制调用持续时间)。因此 RestTemplate 仅被调用一次,传递空的Person(请参阅代码以进行说明)。可以克隆链接的示例项目并在运行测试时展示问题。

Code of application-test.yml(也在这里:链接到 application-test.yml https://github.com/SidekickJohn/demo/blob/main/src/test/resources/application-test.yml):

resilience4j:
  timelimiter:
    configs:
      default:
        timeoutDuration: 5s
        cancelRunningFuture: true
    instances:
      MY_RESILIENCE_KEY:
        baseConfig: default
  retry:
    configs:
      default:
        maxRetryAttempts: 2
        waitDuration: 100ms
        retryExceptions:
          - java.util.concurrent.TimeoutException
    instances:
      MY_RESILIENCE_KEY:
        baseConfig: default

此测试的代码(也在这里:链接到 IntegrationTest.java https://github.com/SidekickJohn/demo/blob/main/src/test/java/com/sidekickjohn/demo/IntegrationTest.java):

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableAutoConfiguration
@ActiveProfiles("test")
public class IntegrationTest {
    private TestRestTemplate testRestTemplate;
    public final String FANCY_URL = "https://my-fancy-url-doesnt-matter.com/person";
    private String apiUrl;
    private HttpHeaders headers;
    
    @LocalServerPort
    private String localServerPort;
    
    @MockBean
    RestTemplate restTemplate;
    
    @Autowired
    CallExternalService callExternalService;
    
    @Autowired
    SimpleRestEndpointController simpleRestEndpointController;
    
    @Before
    public void setup() {
        this.headers = new HttpHeaders();
        this.testRestTemplate = new TestRestTemplate("username", "password");
        this.apiUrl = String.format("http://localhost:%s/person", localServerPort);
    }
    
    @Test
    public void testShouldRetryOnceWhenTimelimitIsReached() {
        // Arrange
        Person mockPerson = new Person();
        mockPerson.setId(1);
        mockPerson.setFirstName("First");
        mockPerson.setLastName("Last");
        ResponseEntity<Person> mockResponse = new ResponseEntity<>(mockPerson, HttpStatus.OK);
            
        
        Answer customAnswer = new Answer() {
            private int invocationCount = 0;
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                invocationCount++;
                if (invocationCount == 1) {
                    Thread.sleep(6000);
                    return new ResponseEntity<>(new Person(), HttpStatus.OK);
                } else {
                    return mockResponse;
                }
            }
        };
        
        doAnswer(customAnswer)
        .when(restTemplate).exchange(
                FANCY_URL,
                HttpMethod.GET,
                new HttpEntity<>(headers),
                new ParameterizedTypeReference<Person>() {});
        
        
        // Act
        ResponseEntity<Person> result = null;
        try {
            result = this.testRestTemplate.exchange(
                    apiUrl,
                    HttpMethod.GET,
                    new HttpEntity<>(headers),
                    new ParameterizedTypeReference<Person>() {
                    });
        } catch(Exception ex) {
            System.out.println(ex);         
        }
        
        
        // Assert
        verify(restTemplate, times(2)).exchange(
                FANCY_URL,
                HttpMethod.GET,
                new HttpEntity<>(headers),
                new ParameterizedTypeReference<Person>() {});
        Assert.assertNotNull(result);
        Assert.assertEquals(mockPerson, result.getBody());      
        
    }
}

我的应用程序的代码显示了问题: https://github.com/SidekickJohn/demo https://github.com/SidekickJohn/demo

我创建了一个“逻辑”泳道图作为 README.md 的一部分:https://github.com/SidekickJohn/demo/blob/main/README.md https://github.com/SidekickJohn/demo/blob/main/README.md


如果你想嘲笑真实的RestTemplate你使用的beanCallExternalService ,你必须使用 Mockito Spy ->https://www.baeldung.com/mockito-spy https://www.baeldung.com/mockito-spy

但我通常更喜欢并推荐使用 WireMock 而不是 Mockito 来模拟 HTTP 端点。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用注释式 Resilience4j 测试 SpringBoot 的相关文章

随机推荐

  • 如何检测鼠标指针位于浏览器关闭按钮上时的事件? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 换句话说 这是用于检测事件的 javascript jquery 代码当鼠标指针位于浏览器的关闭按钮 X按钮 上时 或者当鼠标指针进入
  • 有没有一种方法可以在不测试整个应用程序的情况下对类库(通用 Windows)进行单元测试?

    我需要进行单元测试Class Library Universal Windows 但是从 添加新项目 窗口中 我只看到了这一点 该项目使用应用程序容器运行所有测试 它始终打开一个应用程序 由于我不需要所有这些开销 我想知道是否有一种方法可以
  • Thymeleaf 文本文字中的下划线

    问题 如何转义文本中的多个连续下划线 我正在使用 HTML 的标准 Thymeleaf 方言 我是not此处使用 Spring 或 SpEL 在 Thymeleaf 中 我可以创建一个下划线作为文字文字 https www thymelea
  • 在 Django 中使用多处理时,应用程序尚未加载,出现异常

    我正在做一个 Django 项目并尝试提高后端的计算速度 该任务类似于 CPU 限制的转换过程 这是我的环境 Python 3 6 1 姜戈 1 10 PostgreSQL 9 6 当我尝试通过 python 多处理库并行计算 API 时
  • 从alertDialog构建器获取editText值

    我是新来的android 我需要得到editText s搜索某些东西的价值 但是当我运行程序时 出现错误null pointer exception EditText etSearch 没有获取它的文本 请帮我 谢谢 public bool
  • 提高 ASP.NET/C# 编译速度的最佳方法是什么?

    更新 请将您的答案集中在硬件解决方案上 您使用什么硬件 工具 插件来提高 ASP NET 编译和首次执行速度 我们正在寻找固态硬盘来加快速度 但现在价格确实很高 我现在有两个 RAID 0 的 7200 rpm 硬盘 但我对性能不再满意 所
  • Bootstrap 中的垂直菜单

    有没有办法使用任何引导类来创建垂直菜单 不是下拉菜单 侧边栏上完全独立的垂直菜单 我可以使用我的 css 创建一个 但只是想知道引导程序中是否有任何内置类 或者可以使用顶部栏通过任何 hack 来完成吗 这个问题现在已经很老了 但如果有人看
  • 在 Asp.net Web API 中处理 CORS 预检

    我的架构中有三个应用程序 它们位于同一服务器上 但具有不同的端口号 A Token Application port 4444 Asp net WebApi B API Application port 3333 Asp net WebAp
  • 如何从对象 URL 获取文件或 blob?

    我允许用户通过拖放和其他方法将图像加载到页面中 当图像被删除时 我正在使用URL createObjectURL转换为对象 URL 以显示图像 我不会撤销该网址 因为我会重复使用它 所以 当需要创建一个FormData对象 以便我可以允许他
  • 尝试使用 fopen() 将文件写入不同的目录

    我正在尝试将文件从一个目录写入另一个目录 例如 http www xxxxxxx com admin upload php http www xxxxxxx com admin upload php to http www xxxxxxx
  • onCreateOptionsMenu(菜单菜单)是什么

    方法中的Menu和menu两个参数是什么onCreateOptionsMenu Menu menu 以及如何使用这个方法 我还有一个问题为什么this参数用于 Intent intent new Intent this DisplayMes
  • 尝试利用?

    我看到我的 nopCommerce 网站记录了以下搜索 ADw script AD4 alert 202 ADw script AD4 我有点好奇他们想要完成什么 我搜索了一下 似乎是ADw script AD4 以 UTF7 编码为
  • C# 中的异步方法如何工作?

    我在我的一些项目中使用异步方法 我喜欢它 因为它使我的应用程序更具可扩展性 但是 我想知道异步方法如何在后台真正工作 NET 或 Windows 如何知道调用已完成 根据我进行的异步调用的数量 我可以看到创建了新线程 但并不总是 为什么 此
  • (jQuery) 在 cookie 中单击时保存复选框状态

    关于此功能有很多主题 但我似乎无法让它工作 我在谷歌上搜索了这个具体案例 有一堆链接让我来到这里 但奇怪的是我似乎无法让它们工作 我所做的唯一工作如下 http dl dropbox com u 2238080 a old z htm ht
  • Clang 使用 -nostdlib 生成崩溃代码

    我正在尝试为可执行文件设置自己的运行时环境 但无法使用 clang v3 4 1ubuntu1 目标 x86 64 pc linux gnu 来生成没有段错误的可执行文件 我已将问题简化为以下内容 如果我有一个文件 crt1 c 除了满足
  • Laravel 4:Facades 是如何解决的?

    我有点想看看 Laravel 4 发生了什么facades在引擎盖下 我们以这个 Facade 为例 File get someArgs 如果我没有记错的话 逐步 过于简化 的调用将是 static method invocation wh
  • 反应本机文本输入焦点

    我的应用程序中有一个表单 我希望用户能够通过单击 下一步 返回按钮转到下一个 TextInput 我的输入组件 export default class Input extends Component focusNextField next
  • Spark数据框中过滤的多个条件

    我有一个包含四个字段的数据框 其中一个字段名称是 Status 我尝试在 filter 中对数据帧使用 OR 条件 我尝试了以下查询 但没有运气 df2 df1 filter Status 2 Status 3 df2 df1 filter
  • Java中使用final关键字会提高性能吗?

    在 Java 中 我们看到很多地方final可以使用关键字 但其使用并不常见 例如 String str abc System out println str 在上述情况下 str can be final但这通常被忽略 当一个方法永远不会
  • 使用注释式 Resilience4j 测试 SpringBoot

    我正在使用注释式 Resilience4j春季启动应用程序 https github com SidekickJohn demo称为 演示 通过 RestTemplate 调用外部后端时 我想使用 TimeLimiter 和 Retry 来