8

我有这个测试:

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

}

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

4

3 回答 3

12

这是因为您没有使用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 或任何你喜欢的东西ReflectionTestUtils来配置exceptionThrowingBean你喜欢的行为。
  • 您应该在测试中引用服务的接口类型:MyServiceInterface
  • 最后但并非最不重要。几乎所有 Java 开发人员都遵循一个命名约定:类名有first letter of each internal word capitalized

希望有帮助。

于 2016-09-13T22:25:30.620 回答
7

另一种方式:

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

}
于 2019-05-08T19:48:06.287 回答
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,服务将在其他测试中继续使用模拟 bean。你可以在这里阅读更多

第二种选择是创建一个特定于测试的 @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。

于 2017-04-13T15:03:10.917 回答