Core Java

Getting started with Mocking in Java using Mockito

We all write unit tests but the challenge we face at times is that the unit under test might be dependent on other components. And configuring other components for unit testing is definitely an overkill. Instead we can make use of Mocks in place of the other components and continue with the unit testing.

To show how one can use mocks, I have a Data access layer(DAL), basically a class which provides an API for the application to access and modify the data in the data repository. I then unit test the DAL without actually the need to connect to the data repository. The data repository can be a local database or remote database or a file system or any place where we can store and retrieve the data. The use of a DAL class helps us in keeping the data mappers separate from the application code.

Lets create a Java project using maven.

mvn archetype:generate -DgroupId=info.sanaulla -DartifactId=MockitoDemo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

The above creates a folder MockitoDemo and then creates the entire directory structure for source and test files.

Consider the below model class for this example:

package info.sanaulla.models;

import java.util.List;

/**
* Model class for the book details.
*/
public class Book {

  private String isbn;
  private String title;
  private List<String> authors;
  private String publication;
  private Integer yearOfPublication;
  private Integer numberOfPages;
  private String image;

  public Book(String isbn,
              String title,
              List<String> authors,
              String publication,
              Integer yearOfPublication,
              Integer numberOfPages,
              String image){

    this.isbn = isbn;
    this.title = title;
    this.authors = authors;
    this.publication = publication;
    this.yearOfPublication = yearOfPublication;
    this.numberOfPages = numberOfPages;
    this.image = image;

  }

  public String getIsbn() {
    return isbn;
  }

  public String getTitle() {
    return title;
  }

  public List<String> getAuthors() {
    return authors;
  }

  public String getPublication() {
    return publication;
  }

  public Integer getYearOfPublication() {
    return yearOfPublication;
  }

  public Integer getNumberOfPages() {
    return numberOfPages;
  }

  public String getImage() {
    return image;
  }
}

The DAL class for operating on the Book model class is:

package info.sanaulla.dal;

import info.sanaulla.models.Book;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* API layer for persisting and retrieving the Book objects.
*/
public class BookDAL {

  private static BookDAL bookDAL = new BookDAL();

  public List<Book> getAllBooks(){
      return Collections.EMPTY_LIST;
  }

  public Book getBook(String isbn){
      return null;
  }

  public String addBook(Book book){
      return book.getIsbn();
  }

  public String updateBook(Book book){
      return book.getIsbn();
  }

  public static BookDAL getInstance(){
      return bookDAL;
  }
}

The DAL layer above currently has no functionality and we are going to unit test that piece of code (TDD). The DAL layer might communicate with a ORM Mapper or Database API which we are not concerned while designing the API.

Test driving the DAL layer

There are lot of frameworks for Unit testing and mocking in Java but for this example I would be picking JUnit for unit testing and Mockito for mocking. We would have to update the dependency in Maven’s pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>info.sanaulla</groupId>
  <artifactId>MockitoDemo</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>MockitoDemo</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <!-- Dependency for JUnit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <!-- Dependency for Mockito -->
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-all</artifactId>
      <version>1.9.5</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Now lets unit test the BookDAL. During the unit testing we will inject mock data into the BookDAL so that we can complete the testing of the API without depending on the data source.

Initially we will have an empty test class:

public class BookDALTest {
  public void setUp() throws Exception {

  }

  public void testGetAllBooks() throws Exception {

  }

  public void testGetBook() throws Exception {

  }

  public void testAddBook() throws Exception {

  }

  public void testUpdateBook() throws Exception {

  }
}

We will inject the mock BookDAL and mock data in the setUp() as shown below:

public class BookDALTest {

  private static BookDAL mockedBookDAL;
  private static Book book1;
  private static Book book2;

  @BeforeClass
  public static void setUp(){
    //Create mock object of BookDAL
    mockedBookDAL = mock(BookDAL.class);

    //Create few instances of Book class.
    book1 = new Book("8131721019","Compilers Principles",
            Arrays.asList("D. Jeffrey Ulman","Ravi Sethi", "Alfred V. Aho", "Monica S. Lam"),
            "Pearson Education Singapore Pte Ltd", 2008,1009,"BOOK_IMAGE");

    book2 = new Book("9788183331630","Let Us C 13th Edition",
            Arrays.asList("Yashavant Kanetkar"),"BPB PUBLICATIONS", 2012,675,"BOOK_IMAGE");

    //Stubbing the methods of mocked BookDAL with mocked data. 
    when(mockedBookDAL.getAllBooks()).thenReturn(Arrays.asList(book1, book2));
    when(mockedBookDAL.getBook("8131721019")).thenReturn(book1);
    when(mockedBookDAL.addBook(book1)).thenReturn(book1.getIsbn());
    when(mockedBookDAL.updateBook(book1)).thenReturn(book1.getIsbn());
  }

  public void testGetAllBooks() throws Exception {}

  public void testGetBook() throws Exception {}

  public void testAddBook() throws Exception {}

  public void testUpdateBook() throws Exception {}
}

In the above setUp() method I have:

  1. Created a mock object of BookDAL
    BookDAL mockedBookDAL = mock(BookDAL.class);
  2. Stubbed the API of BookDAL with mock data, such that when ever the API is invoked the mocked data is returned.
    //When getAllBooks() is invoked then return the given data and so on for the other methods.
    when(mockedBookDAL.getAllBooks()).thenReturn(Arrays.asList(book1, book2));
    when(mockedBookDAL.getBook("8131721019")).thenReturn(book1);
    when(mockedBookDAL.addBook(book1)).thenReturn(book1.getIsbn());
    when(mockedBookDAL.updateBook(book1)).thenReturn(book1.getIsbn());

Populating the rest of the tests we get:

package info.sanaulla.dal;

import info.sanaulla.models.Book;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.List;

public class BookDALTest {

  private static BookDAL mockedBookDAL;
  private static Book book1;
  private static Book book2;

  @BeforeClass
  public static void setUp(){
    mockedBookDAL = mock(BookDAL.class);
    book1 = new Book("8131721019","Compilers Principles",
            Arrays.asList("D. Jeffrey Ulman","Ravi Sethi", "Alfred V. Aho", "Monica S. Lam"),
            "Pearson Education Singapore Pte Ltd", 2008,1009,"BOOK_IMAGE");

    book2 = new Book("9788183331630","Let Us C 13th Edition",
            Arrays.asList("Yashavant Kanetkar"),"BPB PUBLICATIONS", 2012,675,"BOOK_IMAGE");

    when(mockedBookDAL.getAllBooks()).thenReturn(Arrays.asList(book1, book2));
    when(mockedBookDAL.getBook("8131721019")).thenReturn(book1);
    when(mockedBookDAL.addBook(book1)).thenReturn(book1.getIsbn());

    when(mockedBookDAL.updateBook(book1)).thenReturn(book1.getIsbn());

  }

  @Test
  public void testGetAllBooks() throws Exception {

    List<Book> allBooks = mockedBookDAL.getAllBooks();
    assertEquals(2, allBooks.size());
    Book myBook = allBooks.get(0);
    assertEquals("8131721019", myBook.getIsbn());
    assertEquals("Compilers Principles", myBook.getTitle());
    assertEquals(4, myBook.getAuthors().size());
    assertEquals((Integer)2008, myBook.getYearOfPublication());
    assertEquals((Integer) 1009, myBook.getNumberOfPages());
    assertEquals("Pearson Education Singapore Pte Ltd", myBook.getPublication());
    assertEquals("BOOK_IMAGE", myBook.getImage());
  }

  @Test
  public void testGetBook(){

    String isbn = "8131721019";

    Book myBook = mockedBookDAL.getBook(isbn);

    assertNotNull(myBook);
    assertEquals(isbn, myBook.getIsbn());
    assertEquals("Compilers Principles", myBook.getTitle());
    assertEquals(4, myBook.getAuthors().size());
    assertEquals("Pearson Education Singapore Pte Ltd", myBook.getPublication());
    assertEquals((Integer)2008, myBook.getYearOfPublication());
    assertEquals((Integer)1009, myBook.getNumberOfPages());

  }

  @Test
  public void testAddBook(){
    String isbn = mockedBookDAL.addBook(book1);
    assertNotNull(isbn);
    assertEquals(book1.getIsbn(), isbn);
  }

  @Test
  public void testUpdateBook(){

    String isbn = mockedBookDAL.updateBook(book1);
    assertNotNull(isbn);
    assertEquals(book1.getIsbn(), isbn);
  }
}

One can run the test by using maven command: mvn test. The output is:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running info.sanaulla.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 sec
Running info.sanaulla.dal.BookDALTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.209 sec

Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

So we have been able to test the DAL class without actually configuring the data source by using mocks.
 

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Rudy Visers
Rudy Visers
10 years ago

Thanks for the good tutorial!

Could you please notice that the naming convention of the maven artifacts is to use the lower case?
http://maven.apache.org/guides/mini/guide-naming-conventions.html

I would not put an ‘s’ into the package name model.

Thanks,

-Rudy-

Back to top button