Home > Net >  @SpringBootTest does not autowire JavaMailSender. I setup everything and also created an explicit Be
@SpringBootTest does not autowire JavaMailSender. I setup everything and also created an explicit Be

Time:11-30

I have a very hard time understanding why my JavaMailSender instance is not being created even tho I created an explicit Bean. The IDE is showing me a successful injection but still when I run the integration test and debug the value of javaMailSender it's always null whatever I do.

Here is my test class with the @Configuration class that explicitly creates the JavaMailSender Bean.

// MailServiceTest.java

@SpringBootTest
class MailServiceTest {
    @Autowired
    @Qualifier("testJavaMailSender")
    private JavaMailSender javaMailSender;
    private final MailService mailService = new MailService(javaMailSender);
    private final GreenMail greenMail = new GreenMail();


    @BeforeEach
    public void setup() {
        greenMail.setUser("foo@localhost", "foo", "password");
        greenMail.start();
    }

    @AfterEach
    public void teardown() {
        greenMail.stop();
    }

    @Test
    public void shouldSendNotificationEmailToRecipient() {
        final String expectedText = "An error occured while executing the Jira Connector import sync."
                  "Please see the log file: 'jira-connector.log' in the application directory";

        mailService.notifyAdmin();

        final MimeMessage[] receivedMessage = greenMail.getReceivedMessagesForDomain("my-ag.com");
        final String actual = GreenMailUtil.getBody(receivedMessage[0]);

        assertThat(actual).isEqualTo(expectedText);

    }
}

@Configuration
class MailSenderTestConfiguration {

    @Bean(name = "testJavaMailSender")
    public JavaMailSender testJavaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost("smtp.gmail.com");
        mailSender.setPort(587);

        mailSender.setUsername("[email protected]");
        mailSender.setPassword("password");

        Properties props = mailSender.getJavaMailProperties();
        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.debug", "true");

        return mailSender;
    }
}

I placed a breakpoint at the first line of the shouldSendNotificationEmailToRecipient method and the debugger tells me javaMailSender = null. Any ideas? I tried everything Google & Stackoverflow offered.

CodePudding user response:

This Test passes (all in one .java file):

@SpringBootTest
class DemoApplicationTests {
    @Autowired 
    @Qualifier("testJavaMailSender")
    private JavaMailSender javaMailSender;

    @Test
    void contextLoads() {
        assertNotNull(javaMailSender);
    }

}
@Configuration
class MailSenderTestConfiguration {

    @Bean(name = "testJavaMailSender")
    public JavaMailSender testJavaMailSender() {
        // for simplicity/brevity just:
        return new JavaMailSenderImpl();
    }
}

Whereas this does not (same file, on the second assert):

@SpringBootTest
class DemoApplicationTests {
    @Autowired // this happens "long after" instantiation/construction (of DemoApplicationTests object), but before @Test ;)
    @Qualifier("testJavaMailSender")
    private JavaMailSender javaMailSender;
    
    // This will be FINALLY assigned, before DemoApplicationTests (object) is even (properly) created/before constructor.
    final MailService ms = new MailService(javaMailSender);

    @Test
    void contextLoads() {
        assertNotNull(javaMailSender); // pass!
        assertNotNull(ms.ms); // FAILS here!
    }

}
@Configuration
class MailSenderTestConfiguration {
    @Bean(name = "testJavaMailSender")
    public JavaMailSender testJavaMailSender() {
        return new JavaMailSenderImpl();
    }
}
class MailService {
    final JavaMailSender ms;
    MailService(JavaMailSender ms) {
        this.ms = ms;
    }
}

Problem:

Initialization of MailService (in class body ... i.e. "instance initializer")! (It is done BEFORE object creation(i.e. "construction")/any "spring magic".)

Solution:

Initialize MailService, when JavaMailSender is available!

e.g. move @Autowired to constructor (of Test):

@SpringBootTest
class DemoApplicationTests {
    final MailService ms;

    public DemoApplicationTests(@Autowired @Qualifier("testJavaMailSender") JavaMailSender javaMailSender) {
        ms = new MailService(javaMailSender);
    }

    @Test
    void contextLoads() {
        assertNotNull(ms.ms);
    }

}

And i'd even prefer:

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    private MailService ms;
    @Autowired
    @Qualifier("testJavaMailSender")
    private JavaMailSender javaMailSender;

    @Test
    void contextLoads() {
        assertNotNull(javaMailSender);
        assertSame(javaMailSender, ms.ms); //!
    }
}

@Configuration
class MailSenderTestConfiguration {

    @Bean(name = "testJavaMailSender")
    public JavaMailSender testJavaMailSender() {
        return new JavaMailSenderImpl();
    }

    @Bean
    public MailService mailService(@Qualifier("testJavaMailSender") JavaMailSender javaMailSender) {
        return new MailService(javaMailSender);
    }
}

...to "manage" MailService by spring.

CodePudding user response:

The configuration looks fine. I just run it on my local and it works. The reason may be that your MailSenderTestConfiguration is not invoked - try to put breakpoint inside the testJavaMailSender() method.

I would try to experiment with @SpringBootTest(classes = ....) and/or separate @TestConfiguration class to make sure that the test loads correct context with my test config.

This is my test class (all in one file) that works fine:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootTest
class ApplicationTests {

    @Autowired
    @Qualifier("testJavaMailSender")
    private JavaMailSender javaMailSender;


    @BeforeEach
    public void setup() {
        System.out.println("Do before each!");
    }

    @AfterEach
    public void teardown() {
        System.out.println("Do after each!");
    }

    @Test
    public void shouldSendNotificationEmailToRecipient() {
        System.out.println(javaMailSender.getMessage());

    }
}

@Configuration
class MailSenderTestConfiguration {

    @Bean(name = "testJavaMailSender")
    public JavaMailSender testJavaMailSender() {
        System.out.println("My config works!");
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        return mailSender;
    }
}

class JavaMailSenderImpl implements JavaMailSender {

    @Override
    public String getMessage() {
        return "I'm the one you're looking for!";
    }
}

interface JavaMailSender {
    String getMessage();
}

Output:

My config works!
......
Do before each!
I'm the one you're looking for!
Do after each!
  • Related