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                              http://maven.apache.org/xsd/maven-4.0.0.xsd">     < 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();     } } |
Â