Spring的@Retryable在运行JUnit Test时无法正常工作

时间:2016-09-13 20:42:18

标签: java spring maven junit spring-retry

我有这个测试:

@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;

}

但是,当我运行测试时,我得到一个runtimeexception,导致我认为它没有重试。我做错了吗?

3 个答案:

答案 0 :(得分:6)

这是因为您没有使用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 hook
  2. 指定Spring上下文配置类
  3. 定义spring配置并将服务导入为bean
  4. 启用可重试注释
  5. 还有其他一些陷阱吗?

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

    希望有所帮助。

答案 1 :(得分:2)

我认为你应该让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,服务将在其他测试中继续使用mocked bean。您可以阅读更多here

第二个选项是创建一个特定于测试的@Configuration并在那里模拟依赖的bean。 Spring会选择这个新的模拟bean而不是原始的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。

答案 2 :(得分:0)

另一种方式:

@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();
    }

}