2

I'm trying to create unit/integration test using Junit5 for specific service classes to avoid overload the whole project.

So here I try to run the EmailService with its dependency classes inside, but I got java.lang.IllegalStateException: Failed to load ApplicationContext. Error creating bean with name 'emailSenderService. No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender' available: expected at least 1 bean which qualifies as autowire candidate.'.

Do I must have to run the whole application to test a single service?

build.yml

{
    testImplementation ('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'junit', module: 'junit'
    }
    testImplementation "org.junit.jupiter:junit-jupiter:5.4.1"
}

Service:

public class EmailSenderService {
    private final JavaMailSender sender;
    private final SpringTemplateEngine templateEngine;
    private final MessageSource i18n;
    public EmailSenderService(JavaMailSender sender, SpringTemplateEngine templateEngine,
                              @Qualifier("messageSource") MessageSource i18n) {
        this.sender = sender;
        this.templateEngine = templateEngine;
        this.i18n = i18n;
    }
}

test class:

@SpringBootTest(
        classes = {EmailSenderService.class}
)
@ExtendWith({SpringExtension.class})
class EmailServiceTest {

    private static GreenMail smtp;

    @Autowired
    private EmailSenderService mailService;

    @BeforeAll
    static void init() {
        smtp = new GreenMail(new ServerSetup(3026,null,"smtp"));
        smtp.start();
    }

    @AfterAll
    static void tearDown() {
        smtp.stop();
    }

    @BeforeEach
    void clearUp() throws FolderException {
        smtp.purgeEmailFromAllMailboxes();
    }

    @Test
    void testNewBidRequestEmail() throws MessagingException {
        EmailMessageTemplateDto contact = new EmailMessageTemplateDto("test","[email protected]","test message");
        mailService.sendUserContactEmail(contact);
        Assertions.assertTrue(smtp.waitForIncomingEmail(1));
    }
}

Error:

2019-04-03 14:56:06.146 WARN 732 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'emailSenderService': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} 2019-04-03 14:56:06.153 ERROR 732 --- [
main] o.s.b.d.LoggingFailureAnalysisReporter :

*************************** APPLICATION FAILED TO START


Description:

Parameter 0 of constructor in com.server.server.service.EmailSenderService required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.mail.javamail.JavaMailSender' in your configuration.

2019-04-03 14:56:06.159 ERROR 732 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@342c38f8] to prepare test instance [com.server.server.test.junit.EmailServiceTest@4c7a078]

java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125) ~[spring-test-5.1.5.RELEASE.jar:5.1.5.RELEASE] at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ...

1 Answer 1

1

The problem is that you really don't have a JavaMailSender available (and you wouldn't want a real one during your tests). You have four options:

  • Register a mock/stub JavaMailSender bean in a test configuration.

  • Use auto-configuration to make your EmailSenderService itself @ConditionalOnBean(JavaMailSender.class) and register a stub if there isn't one (this is usually what I do for testing "does the system send transactional mail?").

  • In this case, since you're actually trying to test the EmailSenderService itself, set spring.mail.host: localhost in your application-test properties (or an annotation).

  • Don't use Spring DI at all here. The major advantage of constructor injection is that you can hand-instantiate your beans, and you could new up your EmailSenderService and its dependencies.

If I understand the intended scope of your test, #3 is probably the solution to go with for you.

Sign up to request clarification or add additional context in comments.

2 Comments

1st option: If I use a mock JavaMailSender, will it be able to send email? For 2nd option, I'll try to learn more about the @ConditionalOnBean annotation before I ask some question. About the 3rd option, I already have a default application.yml file with spring.mail.host: localhost before I submitted this question. 4th option, do I need to retrieve any parameter of the main spring application to create new EmailSenderService?
@LunaticJape It won't send real mail if you do that, which is why it's probably not the best option for what you're wanting to test here. In the third option, you should be getting a JavaMailSender; make sure you have spring-boot-starter-mail included, and if it still isn't working, use --debug to get the auto-configuration report and post it. For the option 4, it depends on what you want to test; you don't need Spring at all, but it can be useful to test that your messages and template engine are configured correctly in your Spring context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.