你的位置:首页 > Java教程

[Java教程]MOCKING IN UNIT TESTS WITH MOCKITO


Unit tests should be small tests (atomic), lightweight, and fast. However, an object under test might have dependencies on other objects. It might need to interact with a database, communicate with a mail server, or talk to a web service or a message queue. All these services might not be available during unit testing. Even if they are available, unit testing the object under testalong with its dependencies can take unacceptable amount of time. What if?

  • The web service is not reachable.
  • The database is down for maintenance.
  • The message queue is heavy and slow.

These all defeat the whole purpose of unit tests being atomic, lightweight, and fast. We want unit tests to execute in a few milliseconds. If the unit tests are slow, your builds become slow, which affects the productivity of your development team. The solution is to use mocking, a way to provide test doubles for your classes being tested.

If you’ve been following the SOLID Principles of Object Oriented Programming, and using the Spring Framework for Dependency Injection, mocking becomes a natural solution for unit testing. You don’t really need a database connection. You just need an object that returns the expected result. If you’ve written tightly coupled code, you will have a difficult time using mocks. I’ve seen plenty of legacy code which could not be unit tested because it was so tightly coupled to other dependent objects. This untestable code did not follow the SOLID Principles of Object Oriented Programming, nor did it utilize Dependency Injection.

Mock Objects: Introduction

In unit test, a test double is a replacement of a dependent component (collaborator) of the object under test. A test double provides the same interface as of the collaborator. It may not be the complete interface, but for the functionality required for the test. Also, the test double does not have to behave exactly as the collaborator. The purpose is to mimic the collaborator to make the object under test think that it is actually using the collaborator.

Based on the role played during testing, there can be different types of test doubles, and mock object is one of them. Some other types are dummy object, fake object, and stub.

What makes a mock object different from the others is that it uses behavior verification. It means that the mock object verifies that it (the mock object) is being used correctly by the object under test. If the verification succeeds, it can be considered that the object under test will correctly use the real collaborator.

The Test Scenario

For the test scenario, consider a product ordering service. A client interacts with a DAO to fulfill a product ordering process.

We will start with the Product domain object and the DAO interface, ProductDao.

Product.java

1 package guru.springframework.unittest.mockito;2 3 public class Product {4 5 }

ProductDao.java

1 package guru.springframework.unittest.mockito;2 3 public interface ProductDao {4   int getAvailableProducts(Product product);5   int orderProduct(Product product, int orderedQuantity);6 }

For the purpose of the example, I kept the Product class empty. But in real applications, it will typically be an entity with states having corresponding getter and setter methods, along with any implemented behaviors.

In the ProductDao interface, we declared two methods:

  • The getAvailableProducts() method returns the number of available quantity of a Productpassed to it.
  • The orderProduct() places an order for a product.

The ProductServiceclass that we will write next is what we are interested on – the object under test.

ProductService.java

 1 package guru.springframework.unittest.mockito; 2  3 public class ProductService { 4   private ProductDao productDao; 5   public void setProductDao(ProductDao productDao) { 6     this.productDao = productDao; 7   } 8   public boolean buy(Product product, int orderedQuantity) throws InsufficientProductsException { 9     boolean transactionStatus=false;10     int availableQuantity = productDao.getAvailableProducts(product);11     if (orderedQuantity > availableQuantity) {12       throw new InsufficientProductsException();13     }14     productDao.orderProduct(product, orderedQuantity);15     transactionStatus=true;16     return transactionStatus;17   }18 19 }

The ProductService class above is composed of ProductDao, which is initialized through a setter method. In the buy() method, we called getAvailableProducts() of ProductDao to check if sufficient quantity of the specified product is available. If not, an exception of type InsufficientProductsException is thrown. If sufficient quantity is available, we called the orderProduct() method of ProductDao.

What we now need is to unit test ProductService. But as you can see, ProductService is composed of ProductDao, whose implementations we don’t have yet. It can be a Spring Data JPA implementation retrieving data from a remote database, or an implementation that communicates with a Web service hosting a cloud-based repository – We don’t know. Even if we have an implementation, we will use it later during integration testing, one of the software testing type I wrote earlier on. But now, we are not interested on any external implementations in this unit test.

In unit tests, we should not to be bothered what the implementation is doing. What we want is to test that our ProductService is behaving as expected and that it is able to correctly use its collaborators. For that, we will mock ProductDao and Product using Mockito.

The ProductService class also throws a custom exception, InsufficientProductsException. The code of the exception class is this.

InsufficientProductsException.java

 1 package guru.springframework.unittest.mockito; 2  3 public class InsufficientProductsException extends Exception { 4   private static final long serialVersionUID = 1L; 5   private String message = null; 6   public InsufficientProductsException() { super(); } 7   public InsufficientProductsException(String message) { 8     super(message); 9     this.message = message;10   }11   public InsufficientProductsException(Throwable cause)12   {13     super(cause);14   }15   @Override16   public String toString() {17     return message;18   }19 }

Using Mockito

Mockito is a mocking framework for unit tests written in Java. It is an open source framework available at github. You can use Mockito with JUnit to create and use mock objects during unit testing. To start using Mockito, download the JAR file and place it in your project class. If you are using Maven, you need to add its dependency in the pom.

pom.
 1 <project  2  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3  <modelVersion>4.0.0</modelVersion> 4  <groupId>guru.springframework.unittest.quickstart</groupId> 5  <artifactId>unittest</artifactId> 6  <packaging>jar</packaging> 7  <version>1.0-SNAPSHOT</version> 8  <name>unittest</name> 9  <url>http://maven.apache.org</url>10  <dependencies>11   <dependency>12   <groupId>junit</groupId>13   <artifactId>junit</artifactId>14   <version>4.12</version>15   <scope>test</scope>16   </dependency>17    <dependency>18      <groupId>org.hamcrest</groupId>19      <artifactId>hamcrest-library</artifactId>20      <version>1.3</version>21      <scope>test</scope>22    </dependency>23    <dependency>24      <groupId>org.mockito</groupId>25      <artifactId>mockito-all</artifactId>26      <version>1.9.5</version>27    </dependency>28  </dependencies>29 </project>

Once you have set up the required dependencies, you can start using Mockito. But, before we start any unit tests with mocks, let’s have a quick overview of the key mocking concepts.

Mock Object Creation

For our example, it’s apparent that we need to mock ProductDao and Product. The simplest way is through calls to the mock() method of the Mockito class. The nice thing about Mockito is that it allows creating mock objects of both interfaces and classes without forcing any explicit declarations.

MockCreationTest.java

 1 package guru.springframework.unittest.mockito; 2  3 import org.junit.Before; 4 import org.junit.Test; 5 import static org.junit.Assert.*; 6 import static org.mockito.Mockito.*; 7 public class MockCreationTest { 8   private ProductDao productDao; 9   private Product product;10   @Before11   public void setupMock() {12     product = mock(Product.class);13     productDao = mock(ProductDao.class);14   }15   @Test16   public void testMockCreation(){17     assertNotNull(product);18     assertNotNull(productDao);19   }20 }

An alternative way is to use the @Mock annotation. When you use it, you will need to initialize the mocks with a call to MockitoAnnotations.initMocks(this) or specify MockitoJUnitRunner as the JUnit test runner as @RunWith(MockitoJUnitRunner.class).

MockCreationAnnotationTest.java

 1 package guru.springframework.unittest.mockito; 2  3 import org.junit.Before; 4 import org.junit.Test; 5 import static org.junit.Assert.*; 6 import org.mockito.Mock; 7 import org.mockito.MockitoAnnotations; 8  9 public class MockCreationAnnotationTest {10   @Mock11   private ProductDao productDao;12   @Mock13   private Product product;14   @Before15   public void setupMock() {16    MockitoAnnotations.initMocks(this);17   }18   @Test19   public void testMockCreation(){20     assertNotNull(product);21     assertNotNull(productDao);22   }23 }

Stubbing

Stubbing means simulating the behavior of a mock object’s method. We can stub a method on a mock object by setting up an expectation on the method invocation. For example, we can stub the getAvailableProducts() method of the ProductDao mock to return a specific value when the method is called.

1 . . .2 @Test3 public void testBuy() throws InsufficientProductsException {4   when(productDao.getAvailableProducts(product)).thenReturn(30);5   assertEquals(30,productDao.getAvailableProducts(product));6 }7 . . .

In Line 4 of the code above, we are stubbing getAvailableProducts(product) of ProductDao to return 30. The when() method represents the trigger to start the stubbing and thenReturn()represents the action of the trigger – which in the example code is to return the value 30. InLine 5 with an assertion, we confirmed that the stubbing performed as expected.

Verifying

Our objective is to test ProductService, and unitl now we only mocked Product and ProductDaoand stubbed getAvailableProducts() of ProductDao.

We now want to verify the behavior of the buy() method of ProductService. First, we want to verify whether it’s calling the orderProduct() of ProductDao with the required set of parameters.

1 . . .2 @Test3 public void testBuy() throws InsufficientProductsException {4   when(productDao.getAvailableProducts(product)).thenReturn(30);5   assertEquals(30,productDao.getAvailableProducts(product));6   productService.buy(product, 5);7   verify(productDao).orderProduct(product, 5);8 }9 . . .

In Line 6 we called the buy() method of ProductService that is under test. In Line 7, we verified that theorderProduct() method of the ProductDao mock get’s invoked with the expected set of parameters (that we passed to buy()).

Our test passed. But, not complete yet. We also want to verify:

  • Number of invocations done on a method: The buy() method invokes getAvailableProduct() at least once.
  • Sequence of Invocation: The buy() method first invokes getAvailableProduct(), and thenorderProduct().
  • Exception verification: The buy() method fails with InsufficientProductsException if order quantity passed to it is more than the available quantity returned by getAvailableProduct().
  • Behavior during exception: The buy() method doesn’t invokes orderProduct() whenInsufficientProductsException is thrown.

Here is the complete test code.

ProductServiceTest.java

 1 package guru.springframework.unittest.mockito; 2  3  4 import org.junit.Before; 5 import org.junit.Test; 6 import org.mockito.InOrder; 7 import static org.mockito.Mockito.*; 8 import org.mockito.Mock; 9 10 public class ProductServiceTest {11   private ProductService productService;12   private ProductDao productDao;13   private Product product;14   private int purchaseQuantity = 15;15 16   @Before17   public void setupMock() {18     productService = new ProductService();19     product = mock(Product.class);20     productDao = mock(ProductDao.class);21     productService.setProductDao(productDao);22   }23 24   @Test25   public void testBuy() throws InsufficientProductsException {26     int availableQuantity = 30;27     System.out.println("Stubbing getAvailableProducts(product) to return " + availableQuantity);28     when(productDao.getAvailableProducts(product)).thenReturn(availableQuantity);29     System.out.println("Calling ProductService.buy(product," + purchaseQuantity + ")");30     productService.buy(product, purchaseQuantity);31     System.out.println("Verifying ProductDao(product, " + purchaseQuantity + ") is called");32     verify(productDao).orderProduct(product, purchaseQuantity);33     System.out.println("Verifying getAvailableProducts(product) is called at least once");34     verify(productDao, atLeastOnce()).getAvailableProducts(product);35     System.out.println("Verifying order of method calls on ProductDao: First call getAvailableProducts() followed by orderProduct()");36     InOrder order = inOrder(productDao);37     order.verify(productDao).getAvailableProducts(product);38     order.verify(productDao).orderProduct(product, purchaseQuantity);39 40 41 42   }43 44   @Test(expected = InsufficientProductsException.class)45   public void purchaseWithInsufficientAvailableQuantity() throws InsufficientProductsException {46     int availableQuantity = 3;47     System.out.println("Stubbing getAvailableProducts(product) to return " + availableQuantity);48     when(productDao.getAvailableProducts(product)).thenReturn(availableQuantity);49     try {50       System.out.println("productService.buy(product" + purchaseQuantity + ") should throw InsufficientProductsException");51       productService.buy(product, purchaseQuantity);52     } catch (InsufficientProductsException e) {53       System.out.println("InsufficientProductsException has been thrown");54       verify(productDao, times(0)).orderProduct(product, purchaseQuantity);55       System.out.println("Verified orderProduct(product, " + purchaseQuantity + ") is not called");56       throw e;57     }58   }59 60 }

I have already explained the initial code of the test class above. So we will start with Line 36 – Line 38 where we used the inOrder() method to verify the order of method invocation that the buy() method makes on ProductDao.

Then we wrote a purchaseWithInsufficientAvailableQuantity() test method to check whether anInsufficientProductsException gets thrown, as expected, when an order with quantity more than the available quantity is made. We also verified in Line 54 that if InsufficientProductsException gets thrown, the orderProduct() method is not invoked.

The output of the test is this.

------------------------------------------------------- T E S T S-------------------------------------------------------Running guru.springframework.unittest.mockito.ProductServiceTestStubbing getAvailableProducts(product) to return 30Calling ProductService.buy(product,15)Verifying ProductDao(product, 15) is calledVerifying getAvailableProducts(product) is called at least onceVerifying order of method calls on ProductDao: First call getAvailableProducts() followed by orderProduct()Stubbing getAvailableProducts(product) to return 3productService.buy(product15) should throw InsufficientProductsException InsufficientProductsException has been thrownVerified orderProduct(product, 15) is not calledTests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.077 sec

  

Summary

Mocking in unit testing is extensively used in Enterprise Application Development with Spring. By using Mockito, you can replace the @Autowired components in the class you want to test with mock objects. You will be unit testing controllers by injecting mock services. You will also be setting up services to use mock DAOs to unit test the service layer. To unit test the DAO layer, you will mock the database APIs. The list is endless – It depends on the type of application you are working on and the object under test. If your following the Dependency Inversion Principle and using Dependency Injection, mocking becomes easy.

The Mockito library is a very large and mature mocking library. It is very popular to use for mocking objects in unit tests. Mockito is popular because it is easy to use, and very versatile. I wrote this post as just an introduction to mocking and Mockito. Checkout the official Mockito documentation to learn about all the capabilities of Mockito.