#java #spring #spring-boot #email
Вопрос:
Мне очень трудно понять, почему мой JavaMailSender
экземпляр не создается, даже если я создал явный компонент. Среда IDE показывает мне успешную инъекцию, но все равно, когда я запускаю интеграционный тест и отлаживаю, значение javaMailSender
этого всегда null
зависит от того, что я делаю.
Вот мой тестовый класс с @Configuration
классом, который явно создает JavaMailSender
компонент.
// 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("my.gmail@gmail.com"); 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; } }
Я поставил точку останова в первой строке shouldSendNotificationEmailToRecipient
метода, и отладчик сообщает мне javaMailSender = null
об этом . Есть какие-нибудь идеи? Я перепробовал все, что предлагали Google и Stackoverflow.
Комментарии:
1. Этого не может быть
null
, иначе ваш тест провалится с ошибкой, указывающей@Autowired
, что он не может быть выполнен. Вы используете Junit убедитесь, что вы не смешиваете JUnit 4 и 5 в одном вызове.2. первое предположение:
MailSenderTestConfiguration
улавливается сканированием компонентов? (если это так,) 2. угадайте: ЕстьjavaMailSender == null
илиmailService.javaMailSender == null
??
Ответ №1:
Этот тест проходит (все в одном .файл java):
@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(); } }
В то время как это не так (тот же файл, во втором утверждении):
@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; } }
Проблема:
Инициализация MailService
(в теле класса … т. е. «инициализатор экземпляра»)! (Это делается ДО создания объекта(т. е. «строительства»)/любой «весенней магии».)
Решение:
Инициализируйте MailService
, когда JavaMailSender
это доступно!
например, перейти @Autowired
к конструктору (теста):
@SpringBootTest class DemoApplicationTests { final MailService ms; public DemoApplicationTests(@Autowired @Qualifier("testJavaMailSender") JavaMailSender javaMailSender) { ms = new MailService(javaMailSender); } @Test void contextLoads() { assertNotNull(ms.ms); } }
И я бы даже предпочел:
@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); } }
… «управиться» MailService
к весне.
Ответ №2:
Конфигурация выглядит нормально. Я просто запускаю его на своем локальном компьютере, и он работает. Причина может заключаться в том, что ваша конфигурация MailSenderTestConfiguration не вызывается — попробуйте поместить точку останова в метод testJavaMailSender ().
Я бы попробовал поэкспериментировать с @SpringBootTest(classes = ....)
и/или отдельным @TestConfiguration
классом, чтобы убедиться, что тест загружает правильный контекст с моей конфигурацией теста.
Это мой тестовый класс (все в одном файле), который отлично работает:
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(); }
Выход:
My config works! ...... Do before each! I'm the one you're looking for! Do after each!