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!