Testing Code that requires a mail server
Almost all applications has one common requirement, they need to send an email notifying something to a registered user. It might be an invoice, a confirmation of an action or a password recovery. How to test this use case might be challenging, using mocks and stubs are ok for unit tests, but having a component test that tests the whole stack.
In this post I am going to show you how Docker and MailHog, can help you on testing this part of code.
MailHog is super simple SMTP server for email testing tool for developers:
- Configure your application to use MailHog for SMTP delivery
- View messages in the web UI, or retrieve them with the JSON API
- Optionally release messages to real SMTP servers for delivery
- Docker image with MailHog installed
Notice that since you can retrieve any message sent to mail server using JSON API, it makes really simple to validate if the message has been really delivered and of course assert on any of message fields.
Arquillian Cube is an Arquillian extension that can be used to manager Docker containers in your tests. To use it you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.
Arquillian Cube offers three different ways to define container(s):
- Defining a docker-compose file.
- Defining a Container Object.
- Using Container Object DSL.
In this example I am going to show you Container Object DSL approach, but any of the others works as well.
To use Container Object DSL you simply need to instantiate the ContainerDslRule (in case you are using JUnit Rules) or use Arquillian runner in case of using JUnit, TestNG or Spock. You can read more about Container Object DSL at http://arquillian.org/arquillian-cube/#_arquillian_cube_and_container_object_dsl
As an example of definition of Redis container:
@ClassRule public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6") .withPortBinding(6379); Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379)); jedis.set("foo", "bar");
When running this test, Redis Docker image is started, tests are executed and finally the Docker instance is stopped.
So let’s see how to do the same but instead of Redis using Mailhog Docker image.
It is important to notice that ContainerDslRule is a generic class that can be extended to become more specific to a concrete use case. And this is what we are going to do for
Mailhog.
public class MailhogContainer extends ContainerDslRule { protected MailhogContainer(String image) { super(image); } public static MailhogContainer createDefaultMailhogContainer() { MailhogContainer mailhogContainer = new MailhogContainer("mailhog/mailhog:v1.0.0"); mailhogContainer.withPortBinding(1025, 8025); return mailhogContainer; } public void should_receive_email_with_subject(String subject) { final ArrayList<String> expected = new ArrayList<>(); expected.add(subject); RestAssured.given() .when() .get("http://" + this.getIpAddress() + ":" + this.getBindPort(8025) + "/api/v1/messages") .then() .assertThat() .body("Content.Headers.Subject", CoreMatchers.hasItems(expected)); } }
First of all we need to create a class extending from
ContainerDslRule, so everything is still a JUnit rule, but a customized one. Then we create a factory method which creates the
MailhogContainer object, setting the image name and the binding ports. Finally an assertion method is used to connect to Rest API of Mailhog server to check if there is any message with given subject.
Then we can write a test using this new rule.
public class MailServiceTest { @ClassRule public static MailhogContainer mailhog = MailhogContainer.createDefaultMailhogContainer(); @Test public void should_send_an_email() throws Exception { // given final MailService mailService = new MailService( mailhog.getIpAddress(), mailhog.getBindPort(1025)); // when mailService.send("lollypop", "lolly.pop@mymail.com", "Invoice: 1234", "Thank you very much for buying this product. Here's your invoice"); // then mailhog.should_receive_email_with_subject("Invoice: 1234"); } }
This test just configures MailService class with Docker container configuration, it sends an email and finally it delegates to container object to validate if the email has been received or not.
Notice that putting everything into an object makes this object reusable in other tests and even in other projects. You can create an independent project with all your custom developed container objects and just reuse them importing them as test jar in hosted project.
Code:https://github.com/lordofthejars/mailtest
Published on Java Code Geeks with permission by Alex Soto, partner at our JCG program. See the original article here: Testing Code that requires a mail server Opinions expressed by Java Code Geeks contributors are their own. |