maven Spring的@Retryable在运行JUnit Test时不工作

hkmswyz6  于 2023-10-17  发布在  Maven
关注(0)|答案(4)|浏览(154)

我有这个测试:

@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,这让我相信它没有重试。我做错了吗?

ahy6op9u

ahy6op9u1#

这是因为您没有使用SpringJUnitClassRunner
Mockito和您自己的类没有考虑@Retryable注解。所以你依赖Spring的实现来做到这一点。但是你的测试并没有激活Spring。
SpringJUnit4ClassRunner JavaDoc:
SpringJUnit 4ClassRunner是JUnit的BlockJUnit 4ClassRunner的自定义扩展,它通过TestContextManager和相关的支持类和注解为标准JUnit测试提供Spring TestContext框架的功能。要使用此类,只需使用@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钩子
1.指定Spring上下文配置类
1.定义spring配置并将服务作为bean导入
1.启用可重试注解
还有其他的陷阱吗?

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

希望能帮上忙。

bxgwgixi

bxgwgixi2#

另一种方式:

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

}
ghg1uchk

ghg1uchk3#

我认为应该让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,service将在其他测试中继续使用mocked bean。您可以在这里阅读更多
第二种选择是创建一个特定于测试的@Configuration,并在那里模拟依赖的bean。Spring将选择这个新的mocked 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中。

gpfsuwkq

gpfsuwkq4#

这个解决方案对我很有效:
Gradle:

dependencies {
    implementation 'org.junit.jupiter:junit-jupiter:5.9.0'
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '3.1.3'
    implementation group: 'org.springframework.retry', name: 'spring-retry', version: '2.0.2'
    implementation group: 'org.springframework', name: 'spring-aspects', version: '3.2.4.RELEASE'

    testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.1.3'
}

主要类别:

@SpringBootApplication
@EnableRetry
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

包含@ blog的类:

@Service
public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Retryable(
            retryFor = {RetryException.class},
            maxAttempts = 3,
            backoff = @Backoff(delay = 500)
    )
    public String retryable() {
        return myRepository.call();
    }
}

测试:

@SpringBootTest
class MyControllerTest {

    @SpyBean
    private MyService myService;
    @MockBean
    private MyRepository myRepository;
    @Autowired
    private MyController sut;

    @Test
    void testRetry() {
        Mockito.when(myRepository.call())
                .thenThrow(new RetryException("Fail Retry 1"))
                .thenThrow(new RetryException("Fail Retry 2"))
                .thenReturn("Success!");

        sut.retry();

        Mockito.verify(myRepository, Mockito.times(3)).call();

    }

    @Test
    void testRetry2() {
        Mockito.when(myRepository.call())
                .thenThrow(new RetryException("Fail Retry 1"))
                .thenThrow(new RetryException("Fail Retry 2"))
                .thenThrow(new RetryException("Fail Retry 3"));

        RetryException exception = assertThrows(RetryException.class, () -> {
            sut.retry();
        });

        Mockito.verify(myRepository, Mockito.times(3)).call();
        assertEquals("Fail Retry 3", exception.getMessage());
    }

}

相关问题