7 Awesome Libraries for Java Unit and Integration Testing
Want to improve your Java unit and integration tests? Here are seven libraries that you might find useful in your day-to-day work.
Join the DZone community and get the full member experience.
Join For FreeLooking to improve your unit and integration tests?
I made a short video giving you an overview of 7 libraries that I regularly use when writing any sort of tests in Java, namely:
- AssertJ
- Awaitility
- Mockito
- Wiser
- Memoryfilesystem
- WireMock
- Testcontainers
What’s in the Video?
The video gives a short overview of how to use the tools mentioned above and how they work. In order of appearance:
AssertJ
JUnit comes with its own set of assertions (i.e., assertEquals
) that work for simple use cases but are quite cumbersome to work with in more realistic scenarios. AssertJ is a small library giving you a great set of fluent assertions that you can use as a direct replacement for the default assertions. Not only do they work on core Java classes, but you can also use them to write assertions against XML or JSON files, as well as database tables!
// basic assertions
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
// chaining string specific assertions
assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
(Note: Source Code from AssertJ)
Awaitility
Testing asynchronous workflows is always a pain. As soon as you want to make sure that, for example, a message broker received or sent a specific message, you'll run into race condition problems because your local test code executes faster than any asynchronous code ever would. Awaitility to the rescue: it is a small library that lets you write polling assertions, in a synchronous manner!
@Test
public void updatesCustomerStatus() {
// Publish an asynchronous message to a broker (e.g. RabbitMQ):
messageBroker.publishMessage(updateCustomerStatusMessage);
// Awaitility lets you wait until the asynchronous operation completes:
await().atMost(5, SECONDS).until(customerStatusIsUpdated());
...
}
(Note: Source Code from Awaitility)
Mockito
There comes a time in unit testing when you want to make sure to replace parts of your functionality with mocks. Mockito is a battle-tested library to do just that. You can create mocks, configure them, and write a variety of assertions against those mocks. To top it off, Mockito also integrates nicely with a huge array of third-party libraries, from JUnit to Spring Boot.
// mock creation
List mockedList = mock(List.class);
// or even simpler with Mockito 4.10.0+
// List mockedList = mock();
// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();
// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();
(Note: Source Code from Mockito)
Wiser
Keeping your code as close to production and not just using mocks for everything is a viable strategy. When you want to send emails, for example, you neither need to completely mock out your email code nor actually send them out via Gmail or Amazon SES. Instead, you can boot up a small, embedded Java SMTP server called Wiser.
Wiser wiser = new Wiser();
wiser.setPort(2500); // Default is 25
wiser.start();
Now you can use Java's SMTP API to send emails to Wiser and also ask Wiser to show you what messages it received.
for (WiserMessage message : wiser.getMessages())
{
String envelopeSender = message.getEnvelopeSender();
String envelopeReceiver = message.getEnvelopeReceiver();
MimeMessage mess = message.getMimeMessage();
// now do something fun!
}
(Note: Source Code from Wiser on GitHub)
Memoryfilesystem
If you write a system that heavily relies on files, the question has always been: "How do you test that?" File system access is somewhat slow, and also brittle, especially if you have your developers working on different operating systems. Memoryfilesystem to the rescue! It lets you write tests against a file system that lives completely in memory, but can still simulate OS-specific semantics, from Windows to macOS and Linux.
try (FileSystem fileSystem = MemoryFileSystemBuilder.newEmpty().build()) {
Path p = fileSystem.getPath("p");
System.out.println(Files.exists(p));
}
(Note: Source Code from Memoryfilesystem on GitHub)
WireMock
How to handle flaky 3rd-party REST services or APIs in your tests? Easy! Use WireMock. It lets you create full-blown mocks of any 3rd-party API out there, with a very simple DSL. You can not only specify the specific responses your mocked API will return, but even go so far as to inject random delays and other unspecified behavior into your server or to do some chaos monkey engineering.
// The static DSL will be automatically configured for you
stubFor(get("/static-dsl").willReturn(ok()));
// Instance DSL can be obtained from the runtime info parameter
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(get("/instance-dsl").willReturn(ok()));
// Info such as port numbers is also available
int port = wmRuntimeInfo.getHttpPort();
(Note: Source Code from WireMock)
Testcontainers
Using mocks or embedded replacements for databases, mail servers, or message queues is all nice and dandy, but nothing beats using the real thing. In comes Testcontainers: a small library that allows you to boot up and shut down any Docker container (and thus software) that you need for your tests. This means your test environment can be as close as possible to your production environment.
@Testcontainers
class MixedLifecycleTests {
// will be shared between test methods
@Container
private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer();
// will be started before and stopped after each test method
@Container
private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer()
.withDatabaseName("foo")
.withUsername("foo")
.withPassword("secret");
(Note: Source Code from Testcontainers)
Enjoy the video!
Opinions expressed by DZone contributors are their own.
Comments