【问题标题】:Spring's @Retryable not working when running JUnit Test运行 JUnit 测试时 Spring 的 @Retryable 不起作用
【发布时间】:2016-09-13 20:42:18
【问题描述】:

我有这个测试:

@RunWith(MockitoJUnitRunner.class)
public class myServiceTest {

@InjectMocks
myService subject;

private myService spy;

@Before
public void before() {
    spy = spy(subject);
}

@Test
public void testing() {

    when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
    spy.print1();
    verify(spy, times(3)).print2();
}

然后我有:

@Service("myService")
public class myService extends myAbstractServiceClass {


public String print1() {
    String temp = "";
    temp = print2();
    return temp;
}

 @Retryable
 public String print2() {
     return "completed";
 }
}

然后我有这个接口(我的 abstractService 实现了):

public interface myServiceInterface {

    @Retryable(maxAttempts = 3)
    String print1() throws RuntimeException;

    @Retryable(maxAttempts = 3)
    String print2() throws RuntimeException;

}

但是,我在运行测试时抛出了一个运行时异常,让我相信它没有重试。我做错了吗?

【问题讨论】:

    标签: java spring maven junit spring-retry


    【解决方案1】:

    这是因为您没有使用SpringJUnitClassRunner

    Mockito 和您自己的类没有考虑 @Retryable 注释。所以你依靠 Spring 的实现来做到这一点。但是您的测试没有激活 Spring。

    这是来自 SpringJUnit4ClassRunner JavaDoc:

    SpringJUnit4ClassRunner 是 JUnit 的 BlockJUnit4ClassRunner 的自定义扩展,它通过 TestContextManager 和相关的支持类和注释为标准 JUnit 测试提供 Spring TestContext Framework 的功能。 要使用此类,只需使用 @RunWith(SpringJUnit4ClassRunner.class) 或 @RunWith(SpringRunner.class) 注释基于 JUnit 4 的测试类。

    您应该至少将您的测试类重组为:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes=MyConfig.class)
    public class MyServiceTest {
        @Configuration
        @EnableRetry
        @Import(myService.class)
        public static class MyConfig {}
    ...
    

    我在那里做什么?

    1. 激活 Spring JUnit 挂钩
    2. 指定Spring上下文配置类
    3. 定义 spring 配置并将您的服务作为 bean 导入
    4. 启用可重试注释

    还有其他一些陷阱吗?

    • 是的,您正在使用 Mockito 模拟异常。如果你想像这样用 Spring 测试这种行为,你应该看看Springockito Annotations
    • 但请注意:Springockito 您将完全替换 spring bean,这会迫使您代理重试的调用。你需要一个类似的结构:test -> retryableService -> exceptionThrowingBean。然后你可以使用 Springockito 或任何你喜欢的东西,例如ReflectionTestUtilsexceptionThrowingBean 配置为您喜欢的行为。
    • 您应该在测试中引用服务的接口类型:MyServiceInterface
    • 最后但并非最不重要。几乎所有 Java 开发人员都遵循一个命名约定:类名有first letter of each internal word capitalized

    希望对您有所帮助。

    【讨论】:

      【解决方案2】:

      另一种方式:

      @EnableRetry
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes={ServiceToTest.class})
      public class RetryableTest {
      
          @Autowired
          private ServiceToTest serviceToTest;
      
          @MockBean
          private ComponentInsideTestClass componentInsideTestClass;
      
          @Test
          public void retryableTest(){
              serviceToTest.method();
          }
      
      }
      

      【讨论】:

        【解决方案3】:

        我认为你应该让 Spring 管理 bean,创建适当的代理并处理流程。 如果你想模拟特定的 bean,你可以创建模拟并将它们注入到被测服务中。

        第一个选项可能是解包代理服务,创建模拟并手动注入它们:

        @RunWith(SpringRunner.class)
        @ContextConfiguration(classes = {RetryConfiguration.class})
        @DirtiesContext
        public class TheServiceImplTest {
        
            @Autowired
            private TheService theService;
        
            @Before
            public void setUp(){
                TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
                RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
                ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
            }
        
            @Test
            public void shouldFetch() {
                Assert.assertNotNull(theService);
            }
        }
        

        在这个例子中,我模拟了一个 bean,RetryProperties,并注入到服务中。另请注意,在这种方法中,您正在修改 Spring 缓存的测试应用程序上下文。这意味着如果您不使用@DirtiesContext,服务将在其他测试中继续使用模拟 bean。你可以阅读更多here

        第二个选项是创建一个特定于测试的@Configuration 并在那里模拟依赖的bean。 Spring 将选择这个新的模拟 bean,而不是原来的:

        @RunWith(SpringRunner.class)
        @ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
        public class TheServiceImplSecondTest {
        
            @Autowired
            private TheService theService;
        
            @Test
            public void shouldFetch() {
                Assert.assertNotNull(theService);
            }
        
            @Configuration
            static class TestConfiguration {
                @Bean
                public RetryProperties retryProperties() {
                    return Mockito.mock(RetryProperties.class);
                }
            }
        }
        

        在这个例子中,我们定义了一个特定于测试的配置并将其添加到@ContextConfiguration。

        【讨论】:

          猜你喜欢
          • 2015-05-17
          • 2012-05-29
          • 2016-01-03
          • 2023-03-13
          • 2018-12-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-24
          相关资源
          最近更新 更多