GreenMail testing

GreenMail is open source library we can use to test our mail sending and receiving code.

For sending purposes GreenMail responds like a regular SMTP server but does not deliver any email. Messages can be extracted, verified and modified in our test cases.

Spring integration

To use GreenMail in Springs integration tests we can register GreenMail extension if we need to verify that correct mail is being sent.

We can also use GreenMail with TestContainers.

GreenMail extension

We start by including GreenMail as dependency to our project:

<dependency>
    <groupId>com.icegreen</groupId>
    <artifactId>greenmail-junit5</artifactId>
    <version>2.0.1</version>
    <scope>test</scope>
</dependency>

and defining appropriate spring.mail.* test application.properties

spring.mail.host=localhost
# greenmail default protocol port + 3000 as offset
spring.mail.port=3025
spring.mail.username=user
spring.mail.password=pass
spring.mail.protocol=smtp
# avoid setting this to true when using a per-test-method GreenMail server
spring.mail.test-connection=false

We can then use GreenMail extension in our integration tests

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MailControllerIT {

    @RegisterExtension
    static GreenMailExtension greenMail =
            new GreenMailExtension(ServerSetupTest.SMTP)
                    .withConfiguration(GreenMailConfiguration
                            .aConfig()
                            // same as in test application.properties
                            .withUser("user", "pass"))
                    .withPerMethodLifecycle(false);

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    void shouldSendEmailWithCorrectPayloadToUser() {
        String payload = """
                {
                  "to": "example@mail.com",
                  "subject": "text mail",
                  "text": "example of plain text mail"
                }
                """;

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> request = new HttpEntity<>(payload, headers);

        ResponseEntity<Void> response =
                this.testRestTemplate.postForEntity("/api/v1/mail/text", request, Void.class);

        assertEquals(200, response.getStatusCode().value());

        await()
                .atMost(2, SECONDS)
                .untilAsserted(
                        () -> {
                            MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
                            assertEquals(1, receivedMessages.length);

                            MimeMessage receivedMessage = receivedMessages[0];
                            assertEquals(1, receivedMessage.getAllRecipients().length);
                            assertEquals("example@mail.com", receivedMessage.getAllRecipients()[0].toString());
                            assertEquals("example of plain text mail", GreenMailUtil.getBody(receivedMessage));
                            assertEquals("text mail", receivedMessage.getSubject());
                        });

    }

}

GreenMail with Testcontainers

Testcontainers is library for providing throwaway, lightweight instances of anything that can run in a Docker container.

With Testcontainers we don’t need to mock our test environments. Instead, we define our test dependencies as code, then simply run your tests and containers will be created and then deleted.

Spring provides Testcontainers integrations with:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>

Currently, there’s no specific Testcontainers module for GreenMail, but we can use GenericContainer to verify that our email service indeed send email.

@SpringBootTest
@Testcontainers
public class MailServiceIT {

    @Autowired
    MailService mailService;
    static final String MAIL_USERNAME = "username";
    static final String MAIL_PASSWORD = "user_password";

    @Container
    static GenericContainer<?> greenMailContainer = new GenericContainer(DockerImageName.parse("greenmail/standalone:2.1.2"))
            .waitingFor(Wait.forLogMessage(".*Starting GreenMail standalone.*", 1))
            .withEnv("GREENMAIL_OPTS", "-Dgreenmail.setup.test.smtp -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.users=" + MAIL_USERNAME + ":" + MAIL_PASSWORD)
            .withExposedPorts(ServerSetupTest.SMTP.getPort());

    @DynamicPropertySource
    static void configureMailHost(DynamicPropertyRegistry registry) {
        registry.add("spring.mail.host", greenMailContainer::getHost);
        registry.add("spring.mail.port", greenMailContainer::getFirstMappedPort);
        registry.add("spring.mail.username", () -> MAIL_USERNAME);
        registry.add("spring.mail.password", () -> MAIL_PASSWORD);
    }


    @Test
    void sendingMailWithTextMessageWorks() throws MessagingException, IOException {
        mailService.sendTextMessage("user@mail.com", "text message test", "plain text in mail body");
    }
}

Since Testcontainers modules start on random ports to enable parallel testing and avoid port clashing, we need to wait for GreenMail Docker container startup by listening for appropriate log message and after startup dynamically register spring.mail.* properties by using Springs DynamicPropertyRegistry .

After GreenMail Testcontainers Spring setup, we can verify that our mail service is indeed sending mails.