In addition to the groups
property, JUnit 5 allows you to use an excludedGroups
property to execute all tests that do not have the specified tag. For example, in a development environment, we do not want to execute the production tests, so we could execute the following:
mvn clean test -DexcludedGroups="Production"
This is helpful because in a large application you can have literally thousands of tests. If you wanted to create this environmental differentiation and add some new production tests, you would not want to have to go back and add a development tag to the other 10,000 tests.
Finally, you can add these same groups
and excludedGroups
fields to the surefire plug-in in your Maven POM file. You can also control these fields using Maven profiles. I encourage you to review the JUnit 5 User Guide to learn more about tags.
Introduction to Mock objects using Mockito
Thus far we have only reviewed testing simple methods that do not rely on external dependencies, but this is far from normal for large applications. For example, a business service probably relies on either a database or web service call to retrieve the data that it operates on. So how would we test a method in such a class? And how would we simulate problematic conditions, such as a database connection error or timeout?
The strategy of mock objects is to analyze the class under test and create mock versions of all of its dependencies, creating the scenarios that we want to test. You can do this manually—which is a lot of work—or you could leverage a tool like Mockito, which simplifies the creation and injection of mock objects into your classes. Mockito provides a simple API to create mock implementations of your dependent classes, inject the mocks into your classes, and control the behavior of the mocks.
The example in Listing 9 shows the source code for a simple repository.
Listing 9. Example repository (Repository.java)
package com.javaworld.geekcap.mockito;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
public class Repository {
public List<String> getStuff() throws SQLException {
// Execute Query
// Return results
return Arrays.asList("One", "Two", "Three");
}
}
Listing 10 shows the source code for a service that uses the above repository.
Listing 10. Example service (Service.java)
package com.javaworld.geekcap.mockito;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Service {
private Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public List<String> getStuffWithLengthLessThanFive() {
try {
return repository.getStuff().stream()
.filter(stuff -> stuff.length() < 5)
.collect(Collectors.toList());
} catch (SQLException e) {
return Arrays.asList();
}
}
}
The Repository
in Listing 9 has a single method, getStuff
, that would presumably connect to a database, execute a query, and return the results. In this example, it simply returns a list of three String
s. The Service
in Listing 10 receives the Repository
through its constructor and defines a single method, getStuffWithLengthLessThanFive
, which returns all String
s with a length less than 5. If the repository throws a SQLException
then it returns an empty list.
Unit testing with JUnit 5 and Mockito
Now let's look at how we can test our service using JUnit 5 and Mockito. Listing 11 shows the source code for a ServiceTest
class.
Listing 11. Testing the service (ServiceTest.java)
package com.javaworld.geekcap.mockito;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
@ExtendWith(MockitoExtension.class)
class ServiceTest {
@Mock
Repository repository;
@InjectMocks
Service service;
@Test
void testSuccess() {
// Setup mock scenario
try {
Mockito.when(repository.getStuff()).thenReturn(Arrays.asList("A", "B", "CDEFGHIJK", "12345", "1234"));
} catch (SQLException e) {
e.printStackTrace();
}
// Execute the service that uses the mocked repository
List<String> stuff = service.getStuffWithLengthLessThanFive();
// Validate the response
Assertions.assertNotNull(stuff);
Assertions.assertEquals(3, stuff.size());
}
@Test
void testException() {
// Setup mock scenario
try {
Mockito.when(repository.getStuff()).thenThrow(new SQLException("Connection Exception"));
} catch (SQLException e) {
e.printStackTrace();
}
// Execute the service that uses the mocked repository
List<String> stuff = service.getStuffWithLengthLessThanFive();
// Validate the response
Assertions.assertNotNull(stuff);
Assertions.assertEquals(0, stuff.size());
}
}
The first thing to notice about this test class is that it is annotated with @ExtendWith(MockitoExtension.class)
. The @ExtendWith
annotation is used to load a JUnit 5 extension. JUnit defines an extension API, which allows a third-party vendor like Mockito to hook into the lifecycle of running test classes and add additional functionality. The MockitoExtension
looks at the test class, finds member variables annotated with the @Mock
annotation, and creates a mock implementation of those variables. It then finds member variables annotated with the @InjectMocks
annotation and attempts to inject its mocks into those classes, using either construction injection or setter injection.
In this example, MockitoExtension
finds the @Mock
annotation on the Repository
member variable, so it creates a mock implementation of it and assigns it to the repository
variable. When it sees the @InjectMocks
annotation on the Service
member variable, it creates an instance of the Service
class, passing the mock Repository
to its constructor. This allows us to control the behavior of the mock Repository
class using Mockito's APIs.
In the testSuccess
method, we use the Mockito API to return a specific result set when its getStuff
method is called. The API works as follows: the Mockito::when
defines the condition, which in this case is the invocation of the repository.getStuff()
method. The when()
method returns a org.mockito.stubbing.OngoingStubbing
instance, which defines a set of methods that determine what to do when the specified method is called. In this case, we invoke the thenReturn()
method to tell the stub to return a specific List
of Strings
.
At this point, we have a Service
instance with a mock Repository
. When the Repository
's getStuff
method is called, it returns a list of five known strings. We invoke the Service
's getStuffWithLengthLessThanFive()
method, which will invoke the Repository
's getStuff()
method, and return a filtered list of String
s whose length is less than five. We can then assert that the returned list is not null and that the size of it is three. This process allows us to test the logic in the specific Service
method, with a known response from the Repository
.
The testException
method configures Mockito so that when the Repository
's getStuff()
method is called, it throws an SQLException
. If this happens, the Service
should not throw an exception; rather, it should return an empty list.
Mockito is a powerful tool and we've only scratched the surface of what it can do. If you've ever wondered how you can test abhorrent conditions—such as network, database, timeout, or other I/O error conditions—Mockito is the tool for you, and it works very elegantly with JUnit 5. If you do run into situations that Mockito does not support, such as mocking static member variables or private constructors, then there is another powerful but complex tool called PowerMock.
Conclusion
In this article, I've quickly introduced you to some of the highlights of working with JUnit 5. I showed you how to configure a Maven project to use JUnit 5 and how to write tests using the @Test
and @ParameterizedTest
annotations. I then introduced most of the JUnit 5 lifecycle annotations, reviewed the use and benefits of filter tags, and walked through integrating JUnit 5 with Hamcrest. Finally, I introduced Mockito and demonstrated how to use mock objects to test some of your more robust Java classes and scenarios.
We'll build on this foundation in the second half of this article, where you'll learn how to integrate JUnit 5 with the Spring framework. You'll learn how to use JUnit 5's built-in and third-party classes and extensions to test your Spring web controllers, services, and repositories.
This story, "JUnit 5 tutorial, part 1: Unit testing with JUnit 5, Mockito, and Hamcrest " was originally published by JavaWorld.