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