Test Driven Development using JUnit5 and Mockito
Test Driven Development is the process in which test cases are written before the code that validates those cases. It depends on the repetition of a very short development cycle. Test Driven Development is a technique in which automated Unit tests are used to drive the design and free decoupling of dependencies. In this article via a sample project let us see the Test Driven Development with JUnit5 and Mockito with integration and functional test as a maven project.
Advantages of JUnit5:
- It supports code written from Java 8 onwards making tests more powerful and maintainable. For the sample project, Java 11 with Maven 3.5.2 or higher is taken.
- Display name feature is there. It can be organized hierarchically.
- It can use more than one extension at a time.
Example Project
Project Structure:

As this is the maven project, let us see the necessary dependencies via
pom.xml
XML
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < modelVersion >4.0.0</ modelVersion > < groupId >gfg.springframework</ groupId > < artifactId >sampletest-junit5-mockito</ artifactId > < version >1.0-SNAPSHOT</ version > < name >sampletest-junit5-mockito</ name > < description >Testing Java with JUnit 5</ description > < properties > < project.build.sourceEncoding >UTF-8</ project.build.sourceEncoding > < project.reporting.outputEncoding >UTF-8</ project.reporting.outputEncoding > < java.version >11</ java.version > < maven.compiler.source >${java.version}</ maven.compiler.source > < maven.compiler.target >${java.version}</ maven.compiler.target > < junit-platform.version >5.3.1</ junit-platform.version > </ properties > < dependencies > < dependency > < groupId >javax.validation</ groupId > < artifactId >validation-api</ artifactId > < version >2.0.1.Final</ version > </ dependency > < dependency > < groupId >org.apache.commons</ groupId > < artifactId >commons-lang3</ artifactId > < version >3.8.1</ version > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < version >${junit-platform.version}</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-params</ artifactId > < version >${junit-platform.version}</ version > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-engine</ artifactId > < version >${junit-platform.version}</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.assertj</ groupId > < artifactId >assertj-core</ artifactId > < version >3.11.1</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.hamcrest</ groupId > < artifactId >hamcrest-library</ artifactId > < version >1.3</ version > < scope >test</ scope > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-compiler-plugin</ artifactId > < version >3.8.0</ version > </ plugin > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-plugin</ artifactId > < version >2.22.0</ version > < configuration > < argLine > --illegal-access=permit </ argLine > </ configuration > </ plugin > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-failsafe-plugin</ artifactId > < version >2.22.0</ version > < configuration > < argLine > --illegal-access=permit </ argLine > </ configuration > < executions > < execution > < goals > < goal >integration-test</ goal > < goal >verify</ goal > </ goals > </ execution > </ executions > </ plugin > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-site-plugin</ artifactId > < version >3.7.1</ version > </ plugin > </ plugins > </ build > < reporting > < plugins > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-report-plugin</ artifactId > < version >2.22.0</ version > </ plugin > </ plugins > </ reporting > </ project > |
Let us see the very very important files of the project. Let’s start with the Model class
BaseEntity.java
Java
import java.io.Serializable; public class BaseEntity implements Serializable { private Long id; public boolean isNew() { return this .id == null ; } public BaseEntity() { } public BaseEntity(Long id) { this .id = id; } public Long getId() { return id; } public void setId(Long id) { this .id = id; } } |
Geek.java
Java
public class Geek extends BaseEntity { public Geek(Long id, String firstName, String lastName) { super (id); this .firstName = firstName; this .lastName = lastName; } private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } } |
Author.java
Java
public class Author extends Geek { private String address; private String city; private String telephone; public Author(Long id, String firstName, String lastName) { super (id, firstName, lastName); } public String getAddress() { return address; } public void setAddress(String address) { this .address = address; } public String getCity() { return city; } public void setCity(String city) { this .city = city; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this .telephone = telephone; } } |
AuthorType.java
Java
public enum AuthorType { FREELANCING, COMPANY } |
AuthorController.java
Java
import javax.validation.Valid; import gfg.springframework.model.Author; import gfg.springframework.services.AuthorService; import gfg.springframework.spring.BindingResult; import gfg.springframework.spring.Model; import gfg.springframework.spring.ModelAndView; import gfg.springframework.spring.WebDataBinder; import java.util.List; public class AuthorController { private static final String VIEWS_AUTHOR_CREATE_OR_UPDATE_FORM = "authors/createOrUpdateAuthorForm" ; private final AuthorService authorService; public AuthorController(AuthorService authorService) { this .authorService = authorService; } public void setAllowedFields(WebDataBinder dataBinder) { dataBinder.setDisallowedFields( "id" ); } public String findAuthors(Model model){ model.addAttribute( "author" , new Author( null , null , null )); return "authors/findAuthors" ; } public String processFindForm(Author author, BindingResult result, Model model){ // allow parameterless GET request for // authors to return all records if (author.getLastName() == null ) { // empty string signifies // broadest possible search author.setLastName( "" ); } // find authors by last name List<Author> results = authorService.findAllByLastNameLike( "%" + author.getLastName() + "%" ); if (results.isEmpty()) { // no authors found result.rejectValue( "lastName" , "notFound" , "not found" ); return "authors/findAuthors" ; } else if (results.size() == 1 ) { // 1 author found author = results.get( 0 ); return "redirect:/authors/" + author.getId(); } else { // multiple authors found model.addAttribute( "selections" , results); return "authors/authorsList" ; } } public ModelAndView showAuthor(Long authorId) { ModelAndView mav = new ModelAndView( "authors/authorDetails" ); mav.addObject(authorService.findById(authorId)); return mav; } public String initCreationForm(Model model) { model.addAttribute( "author" , new Author( null , null , null )); return VIEWS_AUTHOR_CREATE_OR_UPDATE_FORM; } public String processCreationForm( @Valid Author author, BindingResult result) { if (result.hasErrors()) { return VIEWS_AUTHOR_CREATE_OR_UPDATE_FORM; } else { Author savedAuthor = authorService.save(author); return "redirect:/authors/" + savedAuthor.getId(); } } public String initUpdateAuthorForm(Long authorId, Model model) { model.addAttribute(authorService.findById(authorId)); return VIEWS_AUTHOR_CREATE_OR_UPDATE_FORM; } public String processUpdateAuthorForm( @Valid Author author, BindingResult result, Long authorId) { if (result.hasErrors()) { return VIEWS_AUTHOR_CREATE_OR_UPDATE_FORM; } else { author.setId(authorId); Author savedAuthor = authorService.save(author); return "redirect:/authors/" + savedAuthor.getId(); } } } |
AuthorRepository.java
Java
import java.util.List; import gfg.springframework.model.Author; public interface AuthorRepository extends CrudRepository<Author, Long> { Author findByLastName(String lastName); List<Author> findAllByLastNameLike(String lastName); } |
AuthorService.java
Java
import java.util.List; import gfg.springframework.model.Author; public interface AuthorService extends CrudService<Author, Long> { Author findByLastName(String lastName); List<Author> findAllByLastNameLike(String lastName); } |
AuthorMapService.java
Java
import java.util.List; import java.util.Set; import gfg.springframework.model.Author; import gfg.springframework.services.AuthorService; public class AuthorMapService extends AbstractMapService<Author, Long> implements AuthorService { @Override public Set<Author> findAll() { return super .findAll(); } @Override public Author findById(Long id) { return super .findById(id); } @Override public Author save(Author object) { if (object != null ){ return super .save(object); } else { return null ; } } @Override public void delete(Author object) { super .delete(object); } @Override public void deleteById(Long id) { super .deleteById(id); } @Override public Author findByLastName(String lastName) { return this .findAll() .stream() .filter(author -> author.getLastName().equalsIgnoreCase(lastName)) .findFirst() .orElse( null ); } } |
Let us see the test files now
ControllerTests.java
Java
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestInstance; @TestInstance (TestInstance.Lifecycle.PER_CLASS) @Tag ( "controllers" ) public interface ControllerTests { @BeforeAll default void beforeAll(){ System.out.println( "beforeAll-Initialization can be done here" ); } } |
ModelRepeatedTests.java
Java
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestInfo; @Tag ( "repeated" ) public interface ModelRepeatedTests { @BeforeEach default void beforeEachConsoleOutputer(TestInfo testInfo, RepetitionInfo repetitionInfo){ System.out.println( "Running Test - " + testInfo.getDisplayName() + " - " + repetitionInfo.getCurrentRepetition() + " | " + repetitionInfo.getTotalRepetitions()); } } |
ModelTests.java
Java
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestInfo; @Tag ( "model" ) public interface ModelTests { @BeforeEach default void beforeEachConsoleOutputer(TestInfo testInfo){ System.out.println( "Running Test - " + testInfo.getDisplayName()); } } |
AuthorTest.java
Java
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import gfg.springframework.model.Author; import gfg.springframework.model.AuthorType; import gfg.springframework.test.ModelTests; class AuthorTest implements ModelTests { @Test void assertionsTest() { Author author = new Author(1l, "Rachel" , "Green" ); author.setCity( "Seatle" ); author.setTelephone( "1002003001" ); assertAll( "Properties Test" , () -> assertAll( "Geek Properties" , () -> assertEquals( "Rachel" , author.getFirstName(), "First Name Did not Match" ), () -> assertEquals( "Green" , author.getLastName())), () -> assertAll( "Author Properties" , () -> assertEquals( "Seatle" , author.getCity(), "City Did Not Match" ), () -> assertEquals( "1002003001" , author.getTelephone()) )); assertThat(author.getCity(), is( "Seatle" )); } @DisplayName ( "Value Source Test" ) @ParameterizedTest (name = "{displayName} - [{index}] {arguments}" ) @ValueSource (strings = { "Spring" , "Framework" , "GFG" }) void valueSourceTest(String val) { System.out.println(val); } @DisplayName ( "Enum Source Test" ) @ParameterizedTest (name = "{displayName} - [{index}] {arguments}" ) @EnumSource (AuthorType. class ) void enumTest(AuthorType authorType) { System.out.println(authorType); } } |
Here we can see that can include more than one annotations
- @DisplayName: Purpose of the test and categorization can be done easily
- @Parameterized Tests – They are built in and adopt the best features from JUnit4Parameterized and JUnitParams of Junit4
It helps to go with @ValueSource. @EmptySource and @NullSource represent a single parameter. On running the above code, we can able to get the below output

GeekTest.java
Java
import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import gfg.springframework.model.Geek; import gfg.springframework.test.ModelTests; class GeekTest implements ModelTests { @Test void groupedAssertions() { // given Geek person = new Geek(1l, "Ross" , "Geller" ); // then assertAll( "Test Props Set" , () -> assertEquals(person.getFirstName(), "Ross" ), () -> assertEquals(person.getLastName(), "Geller" )); } @Test void groupedAssertionMsgs() { // given Geek person = new Geek(1l, "Chandler" , "Bing" ); // then assertAll( "Test Props Set" , () -> assertEquals(person.getFirstName(), "Ross" , "Input First Name is wrong" ), () -> assertEquals(person.getLastName(), "Geller" , "Input Last Name is wrong" )); } } |
On running the above, the first test is ok and second one fails as expected and the actual one does not match

AuthorMapServiceTest.java
Java
import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import gfg.springframework.model.Author; import gfg.springframework.services.map.AuthorMapService; @DisplayName ( "Author Map Service Test - " ) class AuthorMapServiceTest { AuthorMapService authorMapService; @BeforeEach void setUp() { authorMapService = new AuthorMapService(); } @DisplayName ( "Verifying that there are Zero Authors" ) @Test void authorsAreZero() { int authorCount = authorMapService.findAll().size(); assertThat(authorCount).isZero(); } @DisplayName ( "Saving Authors Tests - " ) @Nested class SaveAuthorsTests { @BeforeEach void setUp() { authorMapService.save( new Author(1L, "Before" , "Each" )); } @DisplayName ( "Saving Author" ) @Test void saveAuthor() { Author author = new Author(2L, "Joe" , "Tribbiani" ); Author savedAuthor = authorMapService.save(author); assertThat(savedAuthor).isNotNull(); } @DisplayName ( "Save Authors Tests - " ) @Nested class FindAuthorsTests { @DisplayName ( "Find Author" ) @Test void findAuthor() { Author foundAuthor = authorMapService.findById(1L); assertThat(foundAuthor).isNotNull(); } @DisplayName ( "Find Author Not Found" ) @Test void findAuthorNotFound() { Author foundAuthor = authorMapService.findById(2L); assertThat(foundAuthor).isNull(); } } } @DisplayName ( "Verify Still Zero Authors" ) @Test void authorsAreStillZero() { int authorCount = authorMapService.findAll().size(); assertThat(authorCount).isZero(); } } |

Please Login to comment...