Spring Boot Integration测试抛出错误而无法在另一个maven模块中获取现有bean

时间:2017-09-27 18:10:45

标签: java spring spring-mvc spring-boot integration-testing

我有以下基于maven的项目结构

product-app
    product-web
    product-service
    product-integration-tests

我希望在MailClientService模块中编写一个product-service服务进行集成测试。集成测试显然是在product-integration-tests模块中编写的。

product-web module已添加为product-integration-tests模块的pom中的依赖项。

但问题是在集成测试的运行时期间,它无法构造MailClientService bean并且正在抛出运行时异常。

异常

13:53:01.161 [main] ERROR org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener@20b2475a] to prepare test instance [com.radial.hostedpayments.service.MailClientServiceIntegrationTest@7857fe2]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.radial.hostedpayments.service.MailClientServiceIntegrationTest': Unsatisfied dependency expressed through field 'mailClientService': No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:569) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:385) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) ~[spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) ~[spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener.prepareTestInstance(AutoConfigureReportTestExecutionListener.java:46) ~[spring-boot-test-autoconfigure-1.4.0.RELEASE.jar:1.4.0.RELEASE]
        at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:367) [surefire-junit4-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:274) [surefire-junit4-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:238) [surefire-junit4-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:161) [surefire-junit4-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:290) [surefire-booter-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:242) [surefire-booter-2.19.1.jar:2.19.1]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:121) [surefire-booter-2.19.1.jar:2.19.1]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1406) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1057) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1019) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:566) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
        ... 30 more
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.318 sec <<< FAILURE! - in com.radial.hostedpayments.service.MailClientServiceIntegrationTest
shouldSendMail(com.radial.hostedpayments.service.MailClientServiceIntegrationTest)  Time elapsed: 0.002 sec  <<< ERROR!
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.radial.hostedpayments.service.MailClientServiceIntegrationTest': Unsatisfied dependency expressed through field 'mailClientService': No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}


Results :

Tests in error:
  MailClientServiceIntegrationTest.shouldSendMail » UnsatisfiedDependency Error ...

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0

MailClientServiceIntegrationTest.java(产品集成测试maven模块)

@RunWith(SpringRunner.class)
public class MailClientServiceIntegrationTest {

    @Autowired
    private MailClientService mailClientService;
    private GreenMail smtpServer;

    @Before
    public void setUp() throws Exception {
        smtpServer = new GreenMail(new ServerSetup(25, null, "smtp"));
        smtpServer.start();
    }

    @Test
    public void shouldSendMail() throws Exception {
        //given
        String recipient = "name@dolszewski.com";
        String message = "Test message content";
        //when
        mailClientService.prepareAndSend(recipient, message);
        //then
        assertReceivedMessageContains(message);
    }

    private void assertReceivedMessageContains(String expected) throws IOException, MessagingException {
        MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
        assertEquals(1, receivedMessages.length);
        String content = (String) receivedMessages[0].getContent();
        assertTrue(content.contains(expected));
    }

    @After
    public void tearDown() throws Exception {
        smtpServer.stop();
    }

}

MailClientService.java(产品服务maven模块)

@Service
public class MailClientService {

    @Autowired
    private JavaMailSender mailSender;
    private MailContentBuilder mailContentBuilder;

    @Value("${error.email.from.address}")
    private String emailAddressFrom;
    @Value("${error.email.to.address}")
    private String emailRecipientAddress;

    private String[] emailRecipientAddresses;
    private static final boolean HTML_FLAG = true;
    private static final String ERROR_ALERT_EMAIL_SUBJECT = "Hosted Payment Service - Error Alert on ";
    private static final Logger LOGGER = LoggerFactory.getLogger(MailClientServiceImpl.class);

    public MailClientServiceImpl(final JavaMailSender javaMailSender, final MailContentBuilder emailContentBuilder) {
        this.mailSender = javaMailSender;
        this.mailContentBuilder = emailContentBuilder;
    }

    @Override
    public void prepareAndSend(final String message, final String emailSubject) {
        emailRecipientAddresses = StringUtils.split(emailRecipientAddress, ',');
        MimeMessagePreparator messagePreparator = mimeMessage -> {
            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
            messageHelper.setFrom(emailAddressFrom);
            messageHelper.setTo(emailRecipientAddresses);
            if (StringUtils.isNotBlank(emailSubject)) {
                messageHelper.setSubject(emailSubject);
            } else {
                messageHelper.setSubject(ERROR_ALERT_EMAIL_SUBJECT + CommonUtils.getHostName());
            }

            // Create the HTML body using Thymeleaf
            String htmlContent = mailContentBuilder.buildHtmlTemplating(message);
            messageHelper.setText(htmlContent, HTML_FLAG);
        };

        try {
            // Send email
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Sending email '{}' to: {} ", ERROR_ALERT_EMAIL_SUBJECT, emailRecipientAddress);
            }
            mailSender.send(messagePreparator);
        } catch (MailException e) {
                e.printStackTrace();
                LOGGER.error("Problem with sending alert email to: {}, error message: {}", emailRecipientAddress, e.getMessage());
        }
    }

    @Override
    public void prepareAndSend(final String message) {
        prepareAndSend(message, null);
    }

}     

产品的web / pom.xml中

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <attachClasses>true</attachClasses>
    </configuration>
</plugin>

产品集成的测试/ pom.xml的

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>hosted-payments</artifactId>
    <version>${project.version}</version>
    <classifier>classes</classifier>
</dependency>

1 个答案:

答案 0 :(得分:2)

如果你仔细观察,你可以创建 MailClientService的一个实例,但是Spring不能通过@Autowired注释在你的测试类中注入一个实例:

No qualifying bean of type [com.radial.hostedpayments.service.MailClientService] found for dependency [com.radial.hostedpayments.service.MailClientService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException

您需要为测试类提供MailClientService类的Spring bean实例。这可以通过@ContextConfiguration批注来完成。

步骤1 - 创建一个公开MailServiceClient的@Configuration类:

@Configuration
public class TestConfig {

    @Bean
    public JavaMailSender mailSender() {
       // example of returning a mock object 
       return Mockito.mock(JavaMailSender.class);
    }    

    @Bean
    public GreenMail smtpServer() {
       // another mock
       return Mockito.mock(GreenMail.class);
    }

    @Bean
    public MailClientService mailClientService(){
        // this could also be used to return a Mock object
        return new MailClientService();
    }
}

步骤2 - 将配置类添加到测试中:

...
@ContextConfiguration(classes = {TestConfig.class})
public class MailClientServiceIntegrationTest {
...

我已对此进行了编辑,以显示如何从配置类返回模拟。请注意,在您的实现中,您自动连接了JavaMailSender,但不是GreenMail变量。

另一个(可能更简单的选项)是使用@MockBean注释而不是Spring Boot testing doc (section 41.3.4)

中描述的自动装配