Unit Test – What is Mocking? and Why?

mockito-logo

In simple, mocking is creating objects that mimic the behavior of real objects. Refer to the following case study :

Tested :

  1. Java 1.8
  2. JUnit 4.12
  3. Mockito 2.0.73-beta
Mock Object
Read this Wikipedia Mock object.

1. Java Examples

A simple Author and Book example.

1.1 BookService to return a list of books by author name.

BookService.java

package com.mkyong.examples.mock;

import java.util.List;

public interface BookService {

    List<Book> findBookByAuthor(String author);

}
BookServiceImpl.java

package com.mkyong.examples.mock;

import java.util.List;

public class BookServiceImpl implements BookService {

    private BookDao bookDao;

    public BookDao getBookDao() {
        return bookDao;
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public List<Book> findBookByAuthor(String name) {
        return bookDao.findBookByAuthor(name);
    }

}
BookDao.java

package com.mkyong.examples.mock;

import java.util.List;

public interface BookDao {

    List<Book> findBookByAuthor(String author);
    
}
BookDaoImpl.java

package com.mkyong.examples.mock;

import java.util.List;

public class BookDaoImpl implements BookDao {

    @Override
    public List<Book> findBookByAuthor(String name) {
		// init database
        // Connect to DB for data
        // return data
    }

}

1.2 A book validator.

BookValidatorService.java

package com.mkyong.examples.mock;

public interface BookValidatorService {

    boolean isValid(Book book);

}
FakeBookValidatorService.java

package com.mkyong.examples.mock;

public class FakeBookValidatorService implements BookValidatorService {

    @Override
    public boolean isValid(Book book) {
        if (book == null)
            return false;

        if ("bot".equals(book.getName())) {
            return false;
        } else {
            return true;
        }

    }
}

1.3 Reviews the AuthorServiceImpl, it has dependencies on BookService (depends on BookDao) and BookValidatorService, it makes the unit test a bit hard to write.

AuthorService.java

package com.mkyong.examples.mock;

public interface AuthorService {

    int getTotalBooks(String author);

}
AuthorServiceImpl.java

package com.mkyong.examples.mock;

import java.util.List;
import java.util.stream.Collectors;

public class AuthorServiceImpl implements AuthorService {

    private BookService bookService;
    private BookValidatorService bookValidatorService;

    public BookValidatorService getBookValidatorService() {
        return bookValidatorService;
    }

    public void setBookValidatorService(BookValidatorService bookValidatorService) {
        this.bookValidatorService = bookValidatorService;
    }

    public BookService getBookService() {
        return bookService;
    }

    public void setBookService(BookService bookService) {
        this.bookService = bookService;
    }

	//How to test this method ???
    @Override
    public int getTotalBooks(String author) {

        List<Book> books = bookService.findBookByAuthor(author);

        //filters some bot writers
        List<Book> filtered = books.stream().filter(
                x -> bookValidatorService.isValid(x))
                .collect(Collectors.toList());

        //other business logic

        return filtered.size();


    }
}

2. Unit Test

Create a unit test for AuthorServiceImpl.getTotalBooks()

2.1 The AuthorServiceImpl has two dependencies, you need to make sure both are configured properly.

AuthorServiceTest.java

package com.mkyong.mock;

import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.examples.mock.BookDaoImpl;
import com.mkyong.examples.mock.BookServiceImpl;
import com.mkyong.examples.mock.FakeBookValidatorService;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class AuthorServiceTest {

    @Test
    public void test_total_book_by_mock() {

		//1. Setup
        AuthorServiceImpl obj = new AuthorServiceImpl();
        BookServiceImpl bookService = new BookServiceImpl();
        bookService.setBookDao(new BookDaoImpl()); //Where Dao connect to?
        obj.setBookService(bookService);
        obj.setBookValidatorService(new FakeBookValidatorService());

		//2. Test method
        int qty = obj.getTotalBooks("mkyong");

		//3. Verify result
        assertThat(qty, is(2));

    }

}

To pass above unit test, you need to setup a database in DAO layer, else bookService will return nothing.

2.3 Some disadvantages to perform tests like above :

  1. This unit test is slow, because you need to start a database in order to get data from DAO.
  2. This unit test is not isolated, it always depends on external resources like database.
  3. This unit test can’t ensures the test condition is always the same, the data in the database may vary in time.
  4. It’s too much work to test a simple method, cause developers skipping the test.

2.4 Solution
The solution is obvious, you need a modified version of the BookServiceImpl class – which will always return the same data for testing, a mock object!

What is mocking?
Again, mocking is creating objects that mimic the behavior of real objects.

3. Unit Test – Mock Object

3.1 Create a new MockBookServiceImpl class and always return the same collection of books for author “mkyong”.

MockBookServiceImpl.java

package com.mkyong.mock;

import com.mkyong.examples.mock.Book;
import com.mkyong.examples.mock.BookService;

import java.util.ArrayList;
import java.util.List;

//I am a mock object!
public class MockBookServiceImpl implements BookService {

    @Override
    public List<Book> findBookByAuthor(String author) {
        List<Book> books = new ArrayList<>();

        if ("mkyong".equals(author)) {
            books.add(new Book("mkyong in action"));
            books.add(new Book("abc in action"));
            books.add(new Book("bot"));
        }

        return books;
    }

    //implements other methods...
    
}

3.2 Update the unit test again.

AuthorServiceTest.java

package com.mkyong.mock;

import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.examples.mock.FakeBookValidatorService;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class AuthorServiceTest {

    @Test
    public void test_total_book_by_mock() {

		//1. Setup
        AuthorServiceImpl obj = new AuthorServiceImpl();

        /*BookServiceImpl bookService = new BookServiceImpl();
        bookService.setBookDao(new BookDaoImpl());
        obj.setBookService(bookService);*/

        obj.setBookService(new MockBookServiceImpl());
        obj.setBookValidatorService(new FakeBookValidatorService());

		//2. Test method
        int qty = obj.getTotalBooks("mkyong");

		//3. Verify result
        assertThat(qty, is(2));

    }

}

The above unit test is much better, fast, isolated (no more database) and the test condition (data) is always same.

3.3 But, there are some disadvantages to create mock object manually like above :

  1. At the end, you may create many mock objects (classes), just for the unit test purpose.
  2. If the interface contains many methods, you need to override each of them.
  3. It’s still too much work, and messy!

3.4 Solution
Try Mockito, a simple and powerful mocking framework.

4. Unit Test – Mockito

4.1 Update the unit test again, this time, create the mock object via Mockito framework.

pom.xml

	<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.0.73-beta</version>
	</dependency>
AuthorServiceTest.java

package com.mkyong.mock;

import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.examples.mock.Book;
import com.mkyong.examples.mock.BookServiceImpl;
import com.mkyong.examples.mock.FakeBookValidatorService;
import org.junit.Test;

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

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class AuthorServiceTest {

	    @Test
	    public void test_total_book_by_mockito() {

			//1. Setup
	        List<Book> books = Arrays.asList(
	                new Book("mkyong in action"),
	                new Book("abc in action"),
	                new Book("bot"));

	        BookServiceImpl mockito = mock(BookServiceImpl.class);

	        //if the author is "mkyong", then return a 'books' object.
	        when(mockito.findBookByAuthor("mkyong")).thenReturn(books);

	        AuthorServiceImpl obj = new AuthorServiceImpl();
	        obj.setBookService(mockito);
	        obj.setBookValidatorService(new FakeBookValidatorService());

			//2. Test method
	        int qty = obj.getTotalBooks("mkyong");
	
			//3. Verify result
	        assertThat(qty, is(2));

	    }

}

Done. Your feedback is appreciated

References

  1. Mockito Official Site
  2. DZone – Mockito
  3. Wikipedia – Mock object
  4. Unit tests with Mockito

About the Author

author image
mkyong
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

avatar
8 Comment threads
0 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
8 Comment authors
AravindSamuel MaleMahyarvenkatArtur Poniedzia?ek Recent comment authors
newest oldest most voted
Vidhya Srinivasan
Guest
Vidhya Srinivasan

Can you please post an example on mockito tests for rest controller class? i have different controller classes accepting oauth access token in their request header. Should i make getting the access token generic to pass to each test controller class? Am new to mockito.

Mahyar
Guest
Mahyar

Great tutorial… Thank you

Aravind
Guest
Aravind

hi, can i have a sample program to mock mybatis autogenerated db pojo’s in spring boot environment?

Samuel Male
Guest
Samuel Male

Thanks very much @mkyong . Its a very real and good example

venkat
Guest
venkat

can you please post an example for mickito junit test for spring3/4 restful services

Artur Poniedzia?ek
Guest
Artur Poniedzia?ek

Could we change the final test when in the class FakeBookValidatorService would be simple:

public boolean isValid(Book book) {
return true;
}
In such a case as I understand we could create simple mock with when and return ?

How do we change the test if in the isValid would be more complicated logic for example calling external WebApi Service to retrieve boolean result ?

Babin Vyacheslav
Guest
Babin Vyacheslav

Thank you!
As always, it is a very nice article!

Nguy?n Qu?c Giang
Guest
Nguy?n Qu?c Giang

Can you share the source code