Open In App

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:



  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:

 

As this is the maven project, let us see the necessary dependencies via 



pom.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




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




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




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




public enum AuthorType {
    FREELANCING, COMPANY
}

AuthorController.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




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




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




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




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




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




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




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

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




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




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

 


Article Tags :