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(); }} |

… [Trackback]
[…] Read More on on that Topic: geeksforgeeks.org/test-driven-development-using-junit5-and-mockito/ […]
… [Trackback]
[…] Find More Info here on that Topic: geeksforgeeks.org/test-driven-development-using-junit5-and-mockito/ […]
… [Trackback]
[…] Find More Info here to that Topic: geeksforgeeks.org/test-driven-development-using-junit5-and-mockito/ […]
… [Trackback]
[…] Read More on that Topic: geeksforgeeks.org/test-driven-development-using-junit5-and-mockito/ […]