Статьи

Тестирование почтового кода в приложении Spring Boot

Логотип-весна-ю

При создании приложения Spring Boot может возникнуть необходимость добавить конфигурацию почты. На самом деле, настройка почты в Spring Boot не сильно отличается от настройки в Spring Bootless приложении. Но как проверить, что конфигурация и отправка почты работают нормально? Давайте посмотрим.

Я предполагаю, что у нас есть простое загрузочное приложение Spring Boot. Если нет, то самый простой способ сделать это — использовать Spring Initializr .

Добавление зависимости javax.mail

Начнем с добавления зависимости javax.mail в build.gradle : compile 'javax.mail:mail:1.4.1' . Нам также потребуется Spring Context Support (если она отсутствует), которая содержит класс поддержки JavaMailSender . Зависимость: compile("org.springframework:spring-context-support")

Конфигурация на основе Java

Spring Boot поддерживает конфигурацию на основе Java. Чтобы добавить конфигурацию почты, мы добавляем класс @Configuration аннотацией @Configuration . Свойства хранятся в mail.properties (но это не обязательно). Значения свойств могут быть введены непосредственно в bean-компоненты с @Value аннотации @Value :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
@PropertySource("classpath:mail.properties")
public class MailConfiguration {
 
    @Value("${mail.protocol}")
    private String protocol;
    @Value("${mail.host}")
    private String host;
    @Value("${mail.port}")
    private int port;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.from}")
    private String from;
    @Value("${mail.username}")
    private String username;
    @Value("${mail.password}")
    private String password;
 
    @Bean
    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        Properties mailProperties = new Properties();
        mailProperties.put("mail.smtp.auth", auth);
        mailProperties.put("mail.smtp.starttls.enable", starttls);
        mailSender.setJavaMailProperties(mailProperties);
        mailSender.setHost(host);
        mailSender.setPort(port);
        mailSender.setProtocol(protocol);
        mailSender.setUsername(username);
        mailSender.setPassword(password);
        return mailSender;
    }
}

Аннотация @PropertySource делает mail.properties доступным для внедрения с помощью @Value . аннотаций. Если это не сделано, вы можете ожидать исключения: java.lang.IllegalArgumentException: Could not resolve placeholder '<name>' in string value "${<name>}" .

И mail.properties :

1
2
3
4
5
6
7
8
mail.protocol=smtp
mail.host=localhost
mail.port=25
mail.smtp.auth=false
mail.smtp.starttls.enable=false
mail.from=me@localhost
mail.username=
mail.password=

Конечная точка почты

Чтобы иметь возможность отправить электронное письмо в нашем приложении, мы можем создать конечную точку REST. Мы можем использовать SimpleMailMessage в Spring для быстрой реализации этой конечной точки. Давайте посмотрим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
class MailSubmissionController {
 
    private final JavaMailSender javaMailSender;
 
    @Autowired
    MailSubmissionController(JavaMailSender javaMailSender) {
        this.javaMailSender = javaMailSender;
    }
 
    @RequestMapping("/mail")
    @ResponseStatus(HttpStatus.CREATED)
    SimpleMailMessage send() {       
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo("someone@localhost");
        mailMessage.setReplyTo("someone@localhost");
        mailMessage.setFrom("someone@localhost");
        mailMessage.setSubject("Lorem ipsum");
        mailMessage.setText("Lorem ipsum dolor sit amet [...]");
        javaMailSender.send(mailMessage);
        return mailMessage;
    }
}

Запуск приложения

Теперь мы готовы запустить приложение. Если вы используете CLI, введите: gradle bootRun , откройте браузер и перейдите к localhost:8080/mail . На самом деле вы должны увидеть ошибку, которая говорит о том, что соединение с почтовым сервером не удалось. Как и ожидалось.

Поддельный SMTP-сервер

FakeSMTP — это бесплатный Fake SMTP-сервер с графическим интерфейсом, написанный на Java, для тестирования электронной почты в приложениях. Мы будем использовать его для проверки работоспособности представления. Пожалуйста, скачайте приложение и просто запустите его, java -jar fakeSMTP-<version>.jar : java -jar fakeSMTP-<version>.jar . После запуска поддельного SMTP-сервера, запустите сервер.

Теперь вы можете снова вызвать конечную точку REST и увидеть результат в Fake SMTP!

Но под тестированием я не имел в виду ручное тестирование! Приложение все еще полезно, но мы хотим автоматически проверить почтовый код.

Модульное тестирование почтового кода

Чтобы иметь возможность автоматически проверять отправку почты, мы будем использовать Wiser — инфраструктуру / утилиту для модульного тестирования почты на основе SubEtha SMTP . Простой низкоуровневый API-интерфейс SubEthaSMTP подходит для написания практически любых приложений для получения почты.

Использование Wiser очень просто. Во-первых, нам нужно добавить тестовую зависимость в build.gradle : testCompile("org.subethamail:subethasmtp:3.1.7") . Во-вторых, мы создаем интеграционный тест с JUnit, Spring и Wiser:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class MailSubmissionControllerTest {
 
    private Wiser wiser;
 
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;
 
 
    @Before
    public void setUp() throws Exception {
        wiser = new Wiser();
        wiser.start();
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
 
    @After
    public void tearDown() throws Exception {
        wiser.stop();
    }
 
    @Test
    public void send() throws Exception {
        // act
        mockMvc.perform(get("/mail"))
                .andExpect(status().isCreated());
        // assert
        assertReceivedMessage(wiser)
                .from("someone@localhosts")
                .to("someone@localhost")
                .withSubject("Lorem ipsum")
                .withContent("Lorem ipsum dolor sit amet [...]");
    }
}

SMTP-сервер инициализируется, запускается в методе @Before и останавливается в методе @Teardown . После отправки сообщения утверждение сделано. Утверждение должно быть создано, поскольку структура не предоставляет никакого. Как вы заметите, нам нужно работать с объектом Wiser, который предоставляет список полученных сообщений:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class WiserAssertions {
 
    private final List<WiserMessage> messages;
 
    public static WiserAssertions assertReceivedMessage(Wiser wiser) {
        return new WiserAssertions(wiser.getMessages());
    }
 
    private WiserAssertions(List<WiserMessage> messages) {
        this.messages = messages;
    }
 
    public WiserAssertions from(String from) {
        findFirstOrElseThrow(m -> m.getEnvelopeSender().equals(from),
                assertionError("No message from [{0}] found!", from));
        return this;
    }
 
    public WiserAssertions to(String to) {
        findFirstOrElseThrow(m -> m.getEnvelopeReceiver().equals(to),
                assertionError("No message to [{0}] found!", to));
        return this;
    }
 
    public WiserAssertions withSubject(String subject) {
        Predicate<WiserMessage> predicate = m -> subject.equals(unchecked(getMimeMessage(m)::getSubject));
        findFirstOrElseThrow(predicate,
                assertionError("No message with subject [{0}] found!", subject));
        return this;
    }
 
    public WiserAssertions withContent(String content) {
        findFirstOrElseThrow(m -> {
            ThrowingSupplier<String> contentAsString =
                () -> ((String) getMimeMessage(m).getContent()).trim();
            return content.equals(unchecked(contentAsString));
        }, assertionError("No message with content [{0}] found!", content));
        return this;
    }
 
    private void findFirstOrElseThrow(Predicate<WiserMessage> predicate, Supplier<AssertionError> exceptionSupplier) {
        messages.stream().filter(predicate)
                .findFirst().orElseThrow(exceptionSupplier);
    }
 
    private MimeMessage getMimeMessage(WiserMessage wiserMessage) {
        return unchecked(wiserMessage::getMimeMessage);
    }
 
    private static Supplier<AssertionError> assertionError(String errorMessage, String... args) {
        return () -> new AssertionError(MessageFormat.format(errorMessage, args));
    }
 
    public static <T> T unchecked(ThrowingSupplier<T> supplier) {
        try {
            return supplier.get();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
 
    interface ThrowingSupplier<T> {
        T get() throws Throwable;
    }
}

Резюме

Всего за пару строк кода мы смогли автоматически протестировать почтовый код. Пример, представленный в этой статье, не сложен, но показывает, как легко начать работу с SubEtha SMTP и Wiser.

Как вы проверяете свой почтовый код?