2016-09-06 1 views
2

Я использую Spring бутс 1.4.0.RELEASE с spring-boot-starter-batch, spring-boot-starter-aop и spring-retryПередразнивали Spring @Service, который имеет @Retryable аннотации методов не удается с UnfinishedVerificationException

У меня есть тест интеграции Spring, который имеет @Service, который высмеивал во время выполнения. Я заметил, что если класс @Service содержит любые аннотации @Retryable по его методам, то он, кажется, вмешивается в Mockito.verify(), я получаю UnfinishedVerificationException. Я полагаю, это должно быть связано с spring-aop? Если я прокомментирую все аннотации @Retryable в @Service, тогда проверьте работу снова.

Я создал github project, который демонстрирует эту проблему.

Он терпит неудачу в sample.batch.MockBatchTestWithRetryVerificationFailures.batchTest() на validateMockitoUsage();

С чем-то вроде:

12:05:36.554 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [[email protected] testClass = MockBatchTestWithRetryVerificationFailures, testInstance = [email protected], testMethod = [email protected], testException = org.mockito.exceptions.misusing.UnfinishedVerificationException: 
Missing method call for verify(mock) here: 
-> at sample.batch.service.MyRetryService$$FastClassBySpringCGLIB$$7573ce2a.invoke(<generated>) 

Example of correct verification: 
    verify(mock).doSomething() 

Однако у меня есть еще один класс (sample.batch.MockBatchTestWithNoRetryWorking.batchTest()) с издевались @Service, который не имеет каких-либо @Retryable аннотацию и проверить работает отлично.

Что я делаю неправильно?

В моей pom.xml У меня есть следующие:

<parent> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-parent</artifactId> 
    <version>1.4.0.RELEASE</version> 
</parent> 
... 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-batch</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-aop</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-test</artifactId> 
     <scope>test</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.retry</groupId> 
     <artifactId>spring-retry</artifactId> 
    </dependency> 
... 

Тогда все связанные классы Java

@SpringBootApplication 
@EnableBatchProcessing 
@Configuration 
@EnableRetry 
public class SampleBatchApplication { 

    @Autowired 
    private JobBuilderFactory jobs; 

    @Autowired 
    private StepBuilderFactory steps; 

    @Autowired 
    private MyRetryService myRetryService; 

    @Autowired 
    private MyServiceNoRetry myServiceNoRetry; 

    @Bean 
    protected Tasklet tasklet() { 

     return new Tasklet() { 
      @Override 
      public RepeatStatus execute(StepContribution contribution, 
        ChunkContext context) { 
       myServiceNoRetry.process(); 
       myRetryService.process(); 
       return RepeatStatus.FINISHED; 
      } 
     }; 

    } 

    @Bean 
    public Job job() throws Exception { 
     return this.jobs.get("job").start(step1()).build(); 
    } 

    @Bean 
    protected Step step1() throws Exception { 
     return this.steps.get("step1").tasklet(tasklet()).build(); 
    } 


    public static void main(String[] args) throws Exception { 
     // System.exit is common for Batch applications since the exit code can be used to 
     // drive a workflow 
     System.exit(SpringApplication 
       .exit(SpringApplication.run(SampleBatchApplication.class, args))); 
    } 

    @Bean 
    ResourcelessTransactionManager transactionManager() { 
     return new ResourcelessTransactionManager(); 
    } 

    @Bean 
    public JobRepository getJobRepo() throws Exception { 
     return new MapJobRepositoryFactoryBean(transactionManager()).getObject(); 
    } 

} 

@Service 
public class MyRetryService { 

    public static final Logger LOG = LoggerFactory.getLogger(MyRetryService.class); 

    @Retryable(maxAttempts = 5, include = RuntimeException.class, backoff = @Backoff(delay = 100, multiplier = 2)) 
    public boolean process() { 

     double random = Math.random(); 

     LOG.info("Running process, random value {}", random); 

     if (random > 0.2d) { 
      throw new RuntimeException("Random fail time!"); 
     } 

     return true; 
    } 

} 

@Service 
public class MyServiceNoRetry { 

    public static final Logger LOG = LoggerFactory.getLogger(MyServiceNoRetry.class); 

    public boolean process() { 

     LOG.info("Running process that doesn't do retry"); 

     return true; 
    } 

} 

@ActiveProfiles("Test") 
@ContextConfiguration(classes = {SampleBatchApplication.class, MockBatchTestWithNoRetryWorking.MockedRetryService.class}, loader = AnnotationConfigContextLoader.class) 
@RunWith(SpringRunner.class) 
public class MockBatchTestWithNoRetryWorking { 

    @Autowired 
    MyServiceNoRetry service; 

    @Test 
    public void batchTest() { 
     service.process(); 

     verify(service).process(); 
     validateMockitoUsage(); 
    } 

    public static class MockedRetryService { 
     @Bean 
     @Primary 
     public MyServiceNoRetry myService() { 
      return mock(MyServiceNoRetry.class); 
     } 
    } 
} 

@ActiveProfiles("Test") 
@ContextConfiguration(classes = { SampleBatchApplication.class, 
     MockBatchTestWithRetryVerificationFailures.MockedRetryService.class }, 
         loader = AnnotationConfigContextLoader.class) 
@RunWith(SpringRunner.class) 
public class MockBatchTestWithRetryVerificationFailures { 

    @Autowired 
    MyRetryService service; 

    @Test 
    public void batchTest() { 
     service.process(); 

     verify(service).process(); 
     validateMockitoUsage(); 
    } 

    public static class MockedRetryService { 
     @Bean 
     @Primary 
     public MyRetryService myRetryService() { 
      return mock(MyRetryService.class); 
     } 
    } 
} 

EDIT: Обновленный вопрос и код на основе образца проекта я поставил вместе, чтобы показать проблема.

+0

Можете ли вы уточнить код – kuhajeyan

+0

@kuhajeyan, конечно, я сделал это сейчас. Надеюсь, я включил достаточно информации –

+1

@kuhajeyan обновлен снова, но с рабочим примером в github, который вы можете попробовать –

ответ

3

Таким образом, после глядя на аналогичной github issue для spring-boot

я обнаружил, что есть дополнительный прокси мешая. Я обнаружил неприятный хак на разворачивание класса АОПА вручную, делает работу по проверке, а именно:

@Test 
public void batchTest() throws Exception { 
    service.process(); 

    if (service instanceof Advised) { 
     service = (MyRetryService) ((Advised) service).getTargetSource().getTarget(); 
    } 

    verify(service).process(); 
    validateMockitoUsage(); 
} 

Будет надеяться, что это может быть закреплено аналогично данным вопрос GitHub. Я подниму вопрос и посмотрю, как далеко доберусь.

EDIT: Поднимался github issue

+0

Я открыл его для 'WebServiceGatewaySupport'. кажется, не решен. https://github.com/spring-projects/spring-boot/issues/11920 –

1

После того, как я видел @Joel Pearsons ответ, и особенно связанный GitHub проблема, я работал вокруг этого, временно используя статический вспомогательный метод, который разворачивает и проверяет:

public static <T> T unwrapAndVerify(T mock, VerificationMode mode) { 
    return ((T) Mockito.verify(AopTestUtils.getTargetObject(mock), mode)); 
} 

С помощью этого метода единственная разница в тестовых случаях - это вызов проверки. Там нет накладных расходов, кроме этого:

unwrapAndVerify(service, times(2)).process(); 

вместо

verify(service, times(2)).process(); 

На самом деле, можно было даже назвать вспомогательный метод как фактический метод Mockito, так что вам нужно только заменить импорт , но мне не понравилось последующее замешательство.

Однако разворот не требуется, если вместо используется @MockBean, чтобы создать издеваемую фасоль. Spring Boot 1.4 поддерживает эту аннотацию.

Смежные вопросы