Open In App

Test Driven Development using JUnit5 and Mockito

Improve
Improve
Like Article
Like
Save
Share
Report

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:

  1. 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.
  2. Display name feature is there. It can be organized hierarchically.
  3. It can use more than one extension at a time.

Example Project

Project Structure:

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"?>
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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

JUnit 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

JUnit Output Failure

 

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


JUnit Output Success

 



Last Updated : 31 Oct, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads