Mockito Essentials

5 (1 reviews total)
By Sujoy Acharya
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
About this book

Whether you are new to JUnit testing and mocking or a seasoned Mockito expert, this book will provide you with the skills you need to successfully build and maintain meaningful JUnit test cases and effectively mock external dependencies. At the beginning, this book deals with dummy and fake objects and then moves on to exploring stubs followed by an example on spying. Then, it will show you how to make legacy code testable, mock external dependencies using Mockito, and write testable code for greenfield projects. You will also gain an insight on the concepts of Service-oriented Architecture (SOA) as well as understand how to unit test RESTful web services with Mockito. By sequentially working through the steps in each chapter, you will quickly learn the features of Mockito.

Mockito Essentials will ensure your success with these concepts, tools, and frameworks.

Publication date:
October 2014
Publisher
Packt
Pages
214
ISBN
9781783983605

 

Chapter 1. Exploring Test Doubles

"I never make stupid mistakes. Only very, very clever ones."

–John Peel

It is very difficult to find stupid mistakes, but it's even more daunting when you are trying to figure out the clever ones. Debugging an application to know how to fix a problem is very expensive and time-consuming. Automated unit tests provide an extremely effective mechanism for catching regressions, especially when combined with test-driven development; it creates a test safety net for the developers.

This chapter covers the concepts of unit testing, quality of unit tests, external dependencies, and test doubles.

The Working with unit tests section introduces you to test automation and describes the characteristics of a good unit test.

The Understanding test doubles section explores the concept of external dependency and provides examples of test doubles. The following test doubles are explored:

  • Dummy objects

  • Stubs

  • Spies

  • Mock objects

  • Fake objects

 

Working with unit tests


A common understanding of unit testing is the testing of the smallest possible part of software, such as a single method, a small set of related methods, or a class.

In reality, we do not test methods; we test a logical unit and its behavior instead. Logical units can extend to a single method, to an entire class, or a collaboration of multiple classes.

For example, a standard calculator program can have an add method for adding two numbers. We can verify the add behavior by invoking the add method, or we can design the calculator program to have a simple calculate API, which can take two numbers and an operation (add, subtract, divide, and so on). Depending on the operand type (integer, double, and so on), the calculator may delegate the calculation to a collaborator class, such as a double calculator or a long calculator. We can still unit test the add behavior, but multiple classes (units) are involved now.

A unit test verifies an assumption about the behavior of the system. Unit tests should be automated to create a safety net so that the assumptions are verified continuously and a quick feedback can be provided if anything goes wrong.

The following are the benefits of test automation:

  • Behavior is continually verified: We refactor code (change the internal structure of the code without affecting the behavior of the system) to improve the code's quality, such as maintainability, readability, or extensibility. We can refactor code with confidence if automated unit tests are running and giving feedback.

  • The side effects of code changes are detected immediately: This is useful for a fragile, tightly-coupled system, where a change in one module breaks another module.

  • Saves time; no need for immediate regression testing: Suppose that you are adding a scientific computational behavior to an existing calculator program and modifying the code; after every piece of change, you do a regression testing to verify the integrity of the system. Manual regression testing is tedious and time-consuming, but if you have an automated unit test suite, then you can delay the regression testing until the functionality is done. This is because the automated suite will inform you at every stage if you break an existing feature.

A unit test should exhibit the following characteristics:

  • It should be automated, as explained in the preceding section.

  • It should have a fast test execution. To be precise, a test should not take more than a few milliseconds to finish execution (they should be fast; the faster, the better). A system can have thousands of unit tests. If they take time to execute, then the overall test execution time will go up; as a result, no one will be interested in running the tests. It will impact the feedback cycle.

  • A test should not depend on the result of another test or rather test execution order. Unit test frameworks can execute tests in any order. So, if a test depends on another test, then the test may fail any time and provide wrong feedback. You want tests to be standalone so that you can look at them and quickly see what they're actually testing, without having to understand the rest of the test code.

  • A test should not depend on database access, file access, or any long running task. Rather, an appropriate test double should isolate the external dependencies.

  • A test result should be consistent and time-and-location transparent. A test should not fail if it is executed at midnight, or it should not fail if it is executed in a different time zone.

  • Tests should be meaningful. A class can have getter and setter methods; you should not write tests for the getters and setters because they should be tested in the process of other more meaningful tests. If they're not, then either you're not testing the functionality or your getters and setters aren't being used at all; so, they're pointless.

  • Tests are system documentation. Tests should be readable and expressive; for example, a test that verifies the unauthorized access could be written as testUnauthorizedAccess() or rather when_an_unauthorized_user_accesses_the_system_then_raises_secuirty_error(). The latter is more readable and expresses the intent of the test.

  • Tests should be short and tests should not be treated as second-class citizens. Code is refactored to improve the quality; similarly, unit tests should be refactored to improve the quality. A test class of 300 lines is not maintainable; we can rather create new test classes, move the tests to the new classes, and create a maintainable suite.

As per the preceding best practices, a test should be executed as fast as possible. Then what should you do if you need to test data access logic or file download code? Simple, do not include the tests in an automated test suite. Consider such tests as slow tests or integration tests. Otherwise, your continuous integration cycle will run for hours. Slow tests should still be automated. However, they may not run all the time, or rather they should be run out of the continuous integration feedback loop.

You cannot automate a unit test if your API class depends on slow external entities, such as data access objects or JNDI lookup. Then, you need test doubles to isolate the external dependencies and automate the unit test.

The next section covers test doubles.

 

Understanding test doubles


We all know about stunt doubles in movies. A stunt double or dummy is a trained replacement used for dangerous action sequences in movies, such as a fight sequence on the top of a burning train, jumping from an airplane, and so on, mainly fight scenes. Stunt doubles are used to protect the real actors, are used when the actor is not available, or when the actor has a contract to not get involved in stunts.

Similarly, sometimes it is not possible to unit test the code because of the unavailability of the collaborator objects, or the cost of interaction and instantiation of collaborators. For instance, when the code is dependent on database access, it is not possible to unit test the code unless the database is available, or when a piece of code needs to send information to a printer and the machine is not connected to a LAN. The primary reason for using doubles is to isolate the unit you are testing from the external dependencies.

Test doubles act as stunt doubles. They are a skilled replacement of the collaborator objects and allow you to unit test code in isolation from the original collaborator.

Gerard Meszaros coined the term test doubles in his book xUNIT TEST PATTERNS, Addison-Wesley—this book explores the various test doubles and sets the foundation for Mockito.

Test doubles can be created to impersonate collaborators and can be categorized into the types, as shown in the following diagram:

 

Using dummy objects


In movies, sometimes a double doesn't perform anything; they just appear on the screen. One such instance would be standing in a crowded place where the real actor cannot go, such as watching a soccer match or tennis match. It will be very risky for the real actor to go to a full house, but the movie's script needs it.

Likewise, a dummy object is passed as a mandatory parameter object. A dummy object is not directly used in the test or code under test, but it is required for the creation of another object required in the code under test. Dummy objects are analogous to null objects, but a dummy object is not used by the code under test. Null objects (as in the pattern) are used in the code under test and are actively interacted with, but they just produce zero behavior. If they weren't used, you'd just use an actual null value. The following steps describe the usage of dummy objects:

Note

In this book, we will write the code and JUnit tests in the Eclipse editor. You can download Eclipse from the following URL:

https://www.eclipse.org/downloads

  1. Launch Eclipse and create a workspace, \PacktPub\Mockito_3605OS\; we'll refer to it as <work_space> in the next steps/chapters.

  2. We'll create an examination grade system. The program will analyze the aggregate of all the subjects and determine the grade of a student. Create a Java project named 3605OS_TestDoubles. Add an enum Grades field to represent a student's grades:

    package com.packt.testdoubles.dummy;
    
    public enum Grades {
       Excellent, VeryGood, Good, Average, Poor;
    }

    Tip

    Downloading the example code

    You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

    We'll use src as our source code's source folder and test as our test code's source folder. All Java files for this example will be created under the com.packt.testdoubles.dummy package.

  3. Create a Student class to uniquely identify a student:

    public class Student {
    
      private final String roleNumber;
      private final String name;
    
      public Student(String roleNumber, String name) {
        this.roleNumber = roleNumber;
        this.name = name;
      }
    
      //setters are ignored
    
    }
  4. Create a Marks class to represent the marks of a student:

    public class Marks {
    
      private final Student student;
      private final String subjectId;
      private final BigDecimal marks;
    
      public Marks(Student student, String subjectId, BigDecimal marks) {
        this.student = student;
        this.subjectId = subjectId;
        this.marks = marks;
      }
      //getters methods go here 
    }

    Note that the Marks constructor accepts a Student object to represent the marks of a student. So, a Student object is needed to create a Marks object.

  5. Create a Teacher class to generate a student's grades:

    public class Teacher {
    
      public Grades generateGrade(List<Marks> marksList) {
    
        BigDecimal aggregate = BigDecimal.ZERO;
    
        for (Marks mark : marksList) {
        aggregate = aggregate.add(mark.getMarks());
      }
    
      BigDecimal percentage = calculatePercent(aggregate, marksList.size());
    
      if (percentage.compareTo(new BigDecimal("90.00")) > 0) {
        return Grades.Excellent;
      }
    
      if (percentage.compareTo(new BigDecimal("75.00")) > 0) {
        return Grades.VeryGood;
      }
    
      if (percentage.compareTo(new BigDecimal("60.00")) > 0) {
        return Grades.Good;
      }
    
      if (percentage.compareTo(new BigDecimal("40.00")) > 0) {
        return Grades.Average;
      }
    
      return Grades.Poor;
    }
    
    private BigDecimal calculatePercent(BigDecimal aggregate,int numberOfSubjects) {
      BigDecimal percent = new BigDecimal(aggregate.doubleValue()/ numberOfSubjects);
      return percent;
    }
  6. Create a DummyStudent class and extend the Student class. This is the dummy object. A dummy object will be the one that is not the real implementation and provides zero functionality or values. The DummyStudent class throws a runtime exception from all the methods. The following is the body of the DummyStudent class:

    public class DummyStudent extends Student {
    
      protected DummyStudent() {
        super(null, null);
      }
    
      public String getRoleNumber() {
        throw new RuntimeException("Dummy student");
      }
    
      public String getName() {
        throw new RuntimeException("Dummy student");
      }
    
    }

    Note that the constructor passes NULL to the super constructor and throws a runtime exception from the getRoleNumber() and getName() methods.

  7. Create a JUnit test to verify our assumption that when a student gets more than 75 percent (but less than 90 percent) in aggregate, then the teacher generates the grade as VeryGood, creates a DummyStudent object, and passes it as Student to the Marks constructor:

    public class TeacherTest {
    
      @Test public void when_marks_above_seventy_five_percent_returns_very_good() {
      DummyStudent dummyStudent = new  DummyStudent();
    
      Marks inEnglish = new Marks(dummyStudent, "English002", new BigDecimal("81.00"));
    
      Marks inMath = new Marks(dummyStudent, "Math005", new BigDecimal("97.00"));
    
      Marks inHistory = new Marks(dummyStudent, "History007, new BigDecimal("79.00"));
      List<Marks> marks = Arrays.asList(inHistory, inMaths, inEnglish);
    
      Grades grade = new Teacher().generateGrade(marks);
      assertEquals(Grades.VeryGood, grade);
      }
    }

    Note that a DummyStudent object is created and passed to all the three Marks objects, as the Marks constructor needs a Student object. This dummyStudent object is not used in the Teacher class or test method, but it is necessary for the Marks object. The dummyStudent object shown in the preceding example is a dummy object.

 

Working with stubs


A stub delivers indirect inputs to the caller when the stub's methods are called. Stubs are programmed only for the test scope. Stubs may record other information such as how many times they are invoked and so on.

Unit testing a happy path is relatively easier than testing an alternate path. For instance, suppose that you need to simulate a hardware failure or transaction timeout scenario in your unit test, or you need to replicate a concurrent money withdrawal for a joint account use case—these scenarios are not easy to imitate. Stubs help us to simulate these conditions. Stubs can also be programmed to return a hardcoded result; for example, a stubbed bank account object can return the account balance as $100.00.

The following steps demonstrate stubbing:

  1. Launch Eclipse, open <work_space>, and go to the 3605OS_TestDoubles project.

  2. Create a com.packt.testdoubles.stub package and add a CreateStudentResponse class. This Plain Old Java Object (POJO) contains a Student object and an error message:

      public class CreateStudentResponse {
        private final String errorMessage;
        private final Student student;
    
        public CreateStudentResponse(String errorMessage, Student student) {
          this.errorMessage = errorMessage;
          this.student = student;
        }
        public boolean isSuccess(){
          return null == errorMessage;
        }
    
        public String getErrorMessage() {
          return errorMessage;
        }
         public Student getStudent() {
         return student;
        }
      }
  3. Create a StudentDAO interface and add a create() method to persist a student's information. The create () method returns the roll number of the new student or throws an SQLException error. The following is the interface definition:

    public interface StudentDAO {
      public String create(String name, String className)
      throws SQLException;
    }
  4. Create an interface and implementation for the student's registration. The following service interface accepts a student's name and a class identifier and registers the student to a class. The create API returns a CreateStudentResponse. The response contains a Student object or an error message:

    public interface StudentService {
      CreateStudentResponse create(String name, String studentOfclass);
    }

    The following is the service implementation:

    public class StudentServiceImpl implements StudentService {
      private final StudentDAO studentDAO;
    
      public StudentServiceImpl(StudentDAO studentDAO) {
        this.studentDAO = studentDAO;
      }
    
      @Override public CreateStudentResponse create(String name, String studentOfclass) {
        CreateStudentResponse response = null;
        try{
          String roleNum= studentDAO.create (name, studentOfclass);
          response = new CreateStudentResponse(null, new Student(roleNum, name));
        }catch(SQLException e) {){
          response = new CreateStudentResponse
          ("SQLException"+e.getMessage(),  null);
        }catch (Exception e) {
          response = new CreateStudentResponse(e.getMessage(), null);
        }
        return response;
      }
    }

    Note

    Note that the service implementation class delegates the Student object's creation task to the StudentDAO object. If anything goes wrong in the data access layer, then the DAO throws an SQLException error. The implementation class catches the exceptions and sets the error message to the response object.

  5. How can you test the SQLException condition? Create a stub object and throw an exception. Whenever the create method is invoked on the stubbed DAO, the DAO throws an exception. The following ConnectionTimedOutStudentDAOStub class implements the StudentDAO interface and throws an SQLException error from the create() method:

    package com.packt.testdoubles.stub;
    import java.sql.SQLException;
    
    public class ConnectionTimedOutStudentDAOStub implements StudentDAO {
      public String create(String name, String className)
      throws SQLException {
        throw new SQLException("DB connection timed out");
      }
    }

    This class should be created under the test source folder since the class is only used in tests.

  6. Test the SQLException condition. Create a test class and pass the stubbed DAO to the service implementation. The following is the test code snippet:

    public class StudentServiceTest {
      private StudentService studentService;
      @Test
      public void when_connection_times_out_then_the_student_is_not_saved() {
        studentService = new StudentServiceImpl(new ConnectionTimedOutStudentDAOStub());
        String classNine = "IX";
        String johnSmith = "john Smith";
        CreateStudentResponse resp = studentService.create(johnSmith, classNine);
        assertFalse(resp.isSuccss());
      }
    }

    The error condition is stubbed and passed into the service implementation object. When the service implementation invokes the create() method on the stubbed DAO, it throws an SQLException error.

Stubs are very handy to impersonate error conditions and external dependencies (you can achieve the same thing with a mock; this is just one approach). Suppose you need to test a code that looks up a JNDI resource and asks the resource to return some value. You cannot look up a JNDI resource from a JUnit test; you can stub the JNDI lookup code and return a stubbed object that will give you a hardcoded value.

 

Exploring a test spy


A spy secretly obtains the information of a rival or someone very important. As the name suggests, a spy object spies on a real object. A spy is a variation of a stub, but instead of only setting the expectation, a spy records the method calls made to the collaborator. A spy can act as an indirect output of the unit under test and can also act as an audit log.

We'll create a spy object and examine its behavior; the following are the steps to create a spy object:

  1. Launch Eclipse, open <work_space>, and go to the 3605OS_TestDoubles project.

  2. Create a com.packt.testdoubles.spy package and create a StudentService class. This class will act as a course register service. The following is the code for the StudentService class:

    public class StudentService {
    
      private Map<String, List<Student>> studentCouseMap = new HashMap<>();
    
      public void enrollToCourse(String courseName,Student student){
        List<Student> list = studentCouseMap.get(courseName);
        if (list == null) {
          list = new ArrayList<>();
        }
    
        if (!list.contains(student)) {
          list.add(student);
        }
        studentCouseMap.put(courseName, list);
      }
    
    }

    The StudentService class contains a map of the course names and students. The enrollToCourse method looks up the map; if no student is enrolled, then it creates a collection of students, adds the student to the collection, and puts the collection back in the map. If a student has previously enrolled for the course, then the map already contains a Student collection. So, it just adds the new student to the collection.students list.

  3. The enrollToCourse method is a void method and doesn't return a response. To verify that the enrollToCourse method was invoked with a specific set of parameters, we can create a spy object. The service will write to the spy log, and the spy will act as an indirect output for verification. Create a spy object to register method invocations. The following code gives the method invocation details:

    class MethodInvocation {
    
      private List<Object> params = new ArrayList<>();
      private Object returnedValue = null;
      private String method;
    
      public List<Object> getParams() {
        return params;
      }
    
      public MethodInvocation addParam(Object parm){
        getParams().add(parm);
        return this;
      }
    
      public Object getReturnedValue() {
        return returnedValue;
      }
    
      public MethodInvocation setReturnedValue(Object returnedValue) {
        this.returnedValue = returnedValue;
        return this;
      }
    
      public String getMethod() {
        return method;
      }
    
      public MethodInvocation setMethod(String method) {
       this.method = method;
        return this;
      }
    }

    The MethodInvocation class represents a method invocation: the method name, a parameter list, and a return value. Suppose a sum() method is invoked with two numbers and the method returns the sum of two numbers, then the MethodInvocation class will contain a method name as sum, a parameter list that will include the two numbers, and a return value that will contain the sum of the two numbers.

    Note

    Note that the setter methods return this(MethodInvocation). This coding approach is known as builder pattern. It helps to build an object in multiple steps. Java StringBuilder is an example of such a use:

    StringBuilder builder = new StringBuilder();
    builder.append("step1").append("step2")…

    The following is the spy object snippet. It has a registerCall method to log a method call instance. It has a map of strings and a List<MethodInvocation> method. If a method is invoked 10 times, then the map will contain the method name and a list of 10 MethodInvocation objects. The spy object provides an invocation method that accepts a method name and returns the method invocation count from the invocationMap class:

    public class StudentServiceSpy {
      private Map<String, List<MethodInvocation>> invocationMap = new HashMap<>();
    
      void registerCall(MethodInvocation invocation) {
        List<MethodInvocation> list = invocationMap.get(invocation.getMethod());
        if (list == null) {
          list = new ArrayList<>();
        }
        if (!list.contains(invocation)) {
          list.add(invocation);
        }
    
        invocationMap.put(invocation.getMethod(), list);
      }
    
      public int invocation(String methodName){
        List<MethodInvocation> list = invocationMap.get(methodName);
        if(list == null){
          return 0;
        }
    
        return list.size();
      }
    
      public MethodInvocation arguments(String methodName, int invocationIndex){
        List<MethodInvocation> list = invocationMap.get(methodName);
        if(list == null || (invocationIndex > list.size())){
          return null;
        }
        return list.get(invocationIndex-1);
      }
    }

    The registerCall method takes a MethodInvocation object and puts it in a map.

  4. Modify the StudentService class to set a spy and log every method invocation to the spy object:

      private StudentServiceSpy spy;
      public void setSpy(StudentServiceSpy spy) {
        this.spy = spy;
      }
      public void enrollToCourse(String courseName, Student student) {
        MethodInvocation invocation = new MethodInvocation();
        invocation.addParam(courseName).addParam(student).setMethod("enrollToCourse");
        spy.registerCall(invocation);
    
        List<Student> list = studentCouseMap.get(courseName);
        if (list == null) {
          list = new ArrayList<>();
        }
        if (!list.contains(student)) {
          list.add(student);
        }
    
        studentCouseMap.put(courseName, list);
      }
  5. Write a test to examine the method invocation and arguments. The following JUnit test uses the spy object and verifies the method invocation:

    public class StudentServiceTest {
      StudentService service = new StudentService();
      StudentServiceSpy spy = new StudentServiceSpy();
    
      @Test
      public void enrolls_students() throws Exception {
        //create student objects
        Student bob = new Student("001", "Robert Anthony");
        Student roy = new Student("002", "Roy Noon");
        //set spy
        service.setSpy(spy);
    
        //enroll Bob and Roy
        service.enrollToCourse("english", bob);
        service.enrollToCourse("history", roy);
        //assert that the method was invoked twice
        assertEquals(2, spy.invocation("enrollToCourse"));
    
        //get the method arguments for the first call
        List<Object> methodArguments = spy.arguments
    ("enrollToCourse", 1).getParams();
    
        //get the method arguments for the 2nd call
        List<Object> methodArguments2 = spy.arguments
    ("enrollToCourse", 2).getParams();
    
        //verify that Bob was enrolled to English first
        assertEquals("english", methodArguments.get(0));
        assertEquals(bob, methodArguments.get(1));
    
        //verify that Roy was enrolled to history
        assertEquals("history", methodArguments2.get(0));
       assertEquals(roy, methodArguments2.get(1));
    
      }
    
    }
 

Getting started with mock objects


A mock object is a combination of a spy and a stub. It acts as an indirect output for a code under test, such as a spy, and can also stub methods to return values or throw exceptions, like a stub. A mock object fails a test if an expected method is not invoked or if the parameters of the method don't match.

The following steps demonstrate the test failure scenario:

  1. Launch Eclipse, open <work_space>, and go to the 3605OS_TestDoubles project.

  2. Create a com.packt.testdoubles.mock package and a StudentService class. This class will act as a course register service. The following is the code for the StudentService class:

    public class StudentService {
    
      private Map<String, List<Student>> studentCouseMap = new HashMap<>();
    
      public void enrollToCourse(String courseName,Student student){
        List<Student> list = studentCouseMap.get(courseName);
        if (list == null) {
          list = new ArrayList<>();
        }
    
        if (!list.contains(student)) {
          list.add(student);
        }
    
        studentCouseMap.put(courseName, list);
      }
    }
  3. Copy the StudentServiceSpy class and rename it as StudentServiceMockObject. Add a new method to verify the method invocations:

    public void verify(String methodName, int numberOfInvocation){
      int actual = invocation(methodName);
      if(actual != numberOfInvocation){
        throw new IllegalStateException(methodName+" was expected ["+numberOfInvocation+"] times but actuallyactaully invoked["+actual+"] times");
      }
    }
  4. Modify the StudentService code to set the mock object, as we did in the spy example:

    private StudentServiceMockObject mock;
    
    public void setMock(StudentServiceMockObject mock) {
      this.mock = mock;
    }
    public void enrollToCourse(String courseName,Student student){
      MethodInvocation invocation = new MethodInvocation();
    
      invocation.addParam(courseName).addParam(student).setMethod("enrollToCourse");
    
      mock.registerCall(invocation);
      …//existing code
    }
  5. Create a test to verify the method invocation:

    public class StudentServiceTest {
      StudentService service = new StudentService();
      StudentServiceMockObject mockObject = new StudentServiceMockObject();
    
      @Test
      public void enrolls_students() throws Exception {
        //create 2 students
        Student bob = new Student("001", "Robert Anthony");
        Student roy = new Student("002", "Roy Noon");
    
        //set mock/spy
        service.setMock(mockObject);
    
        //invoke method twice
        service.enrollToCourse("english", bob);
        service.enrollToCourse("history", roy);
    
        //assert that the method was invoked twice
        assertEquals(2, 
        mockObject.invocation("enrollToCourse"));
    
        //verify wrong information, that enrollToCourse was //invoked once, but actually it is invoked twice
        mockObject.verify("enrollToCourse", 1);
    
      }
    
    }
  6. Run the test; it will fail, and you will get a verification error. The following screenshot shows the JUnit failure output:

The Mockito framework provides an API for mocking objects. It uses proxy objects to verify the invocation and stub calls.

 

Implementing fake objects – simulators


A fake object is a test double with real logic (unlike stubs) and is much more simplified or cheaper in some way. We do not mock or stub a unit that we test; rather, the external dependencies of the unit are mocked or stubbed so that the output of the dependent objects can be controlled or observed from the tests. The fake object replaces the functionality of the real code that we want to test. Fakes are also dependencies, and don't mock via subclassing (which is generally always a bad idea; use composition instead). Fakes aren't just stubbed return values; they use some real logic.

A classic example is to use a database stub that always returns a fixed value from the DB, or a DB fake, which is an entirely in-memory nonpersistent database that's otherwise fully functional.

What does this mean? Why should you test a behavior that is unreal? Fake objects are extensively used in legacy code. The following are the reasons behind using a fake object:

  • The real object cannot be instantiated, such as when the constructor reads a file, performs a JNDI lookup, and so on.

  • The real object has slow methods; for example, a class might have a calculate () method that needs to be unit tested, but the calculate() method calls a load ()method to retrieve data from the database. The load() method needs a real database, and it takes time to retrieve data, so we need to bypass the load() method to unit test the calculate behavior.

Fake objects are working implementations. Mostly, the fake class extends the original class, but it usually performs hacking, which makes it unsuitable for production.

The following steps demonstrate the utility of a fake object. We'll build a program to persist a student's information into a database. A data access object class will take a list of students and loop through the student's objects; if roleNumber is null, then it will insert/create a student, otherwise it will update the existing student's information. We'll unit test the data access object's behavior:

  1. Launch Eclipse, open <work_space>, and go to the 3605OS_TestDoubles project.

  2. Create a com.packt.testdoubles.fake package and create a JdbcSupport class. This class is responsible for database access, such as acquiring a connection, building a statement object, querying the database, updating the table, and so on. We'll hide the JDBC code and just expose a method for the batch update. The following are the class details:

    public class JdbcSupport {
      public int[] batchUpdate(String sql, List<Map<String, Object>> params){
        //original db access code is hidden
        return null;
      }
    }

    Check whether the batchUpdate method takes an SQL string and a list of objects to be persisted. It returns an array of integers. Each array index contains either 0 or 1. If the value returned is 1, it means that the database update is successful, and 0 means there is no update. So, if we pass only one Student object to update and if the update succeeds, then the array will contain only one integer as 1; however, if it fails, then the array will contain 0.

  3. Create a StudentDao interface for the Student data access. The following is the interface snippet:

    public interface StudentDao {
      public void batchUpdate(List<Student> students);
    }
  4. Create an implementation of StudentDao. The following class represents the StudentDao implementation:

    public class StudentDaoImpl implements StudentDao {
    
      public StudentDaoImpl() {
      }
    
      @Override
      public void batchUpdate(List<Student> students) {
    
        List<Student> insertList = new ArrayList<>();
        List<Student> updateList = new ArrayList<>();
    
        for (Student student : students) {
          if (student.getRoleNumber() == null) {
            insertList.add(student);
          } else {
            updateList.add(student);
          }
        }
    
        int rowsInserted = 0;
        int rowsUpdated = 0;
    
        if (!insertList.isEmpty()) {
          List<Map<String, Object>> paramList = new ArrayList<>();
          for (Student std : insertList) {
            Map<String, Object> param = new HashMap<>();
            param.put("name", std.getName());
            paramList.add(param);
          }
    
          int[] rowCount = update("insert", paramList);
          rowsInserted = sum(rowCount);
        }
    
        if (!updateList.isEmpty()) {
          List<Map<String, Object>> paramList = new ArrayList<>();
          for (Student std : updateList) {
            Map<String, Object> param = new HashMap<>();
            param.put("roleId", std.getRoleNumber());
            param.put("name", std.getName());
            paramList.add(param);
          }
    
          int[] rowCount = update("update", paramList);
          rowsUpdated = sum(rowCount);
        }
    
        if (students.size() != (rowsInserted + rowsUpdated)) {
          throw new IllegalStateException("Database update error, expected "   + students.size() + " updates but actual " + (rowsInserted + rowsUpdated));
          }
        }
    
        public int[] update(String sql, List<Map<String, Object>> params) {
          return new JdbcSupport().batchUpdate(sql, params);
        }
    
        private int sum(int[] rows) {
          int sum = 0;
           for (int val : rows) {
             sum += val;
           }
           return sum;
        }
    
      }

    The batchUpdate method creates two lists; one for the new students and the other for the existing students. It loops through the Student list and populates the insertList and udpateList methods, depending on the roleNumber attribute. If roleNumber is NULL, then this implies a new student. It creates a SQL parameter map for each student and calls the JdbcSupprt class, and finally, checks the database update count.

  5. We need to unit test the batchUpdate behavior, but the update method creates a new instance of JdbcSupport and calls the database. So, we cannot directly unit test the batchUpdate() method; it will take forever to finish. Our problem is the update() method; we'll separate the concern, extend the StudentDaoImpl class, and override the update() method. If we invoke batchUpdate() on the new object, then it will route the update() method call to the new overridden update() method.

    Create a StudentDaoTest unit test and a TestableStudentDao subclass:

    public class StudentDaoTest {
      class TestableStudentDao extends StudentDaoImpl{
        int[] valuesToReturn;
        int[] update(String sql, List<Map<String, Object>> params) {
          Integer count = sqlCount.get(sql);
          if(count == null){
            sqlCount.put(sql, params.size());
          }else{
            sqlCount.put(sql, count+params.size());
          }
    
          if (valuesToReturn != null) {
            return valuesToReturn;
          }
    
    
          return valuesToReturn;
        }
      }
    }

    Note that the update method doesn't make a database call; it returns a hardcoded integer array instead. From the test, we can set the expected behavior. Suppose we want to test a database update's fail behavior; here, we need to create an integer array of index 1, set its value to 0, such as int[] val = {0}, and set this array to valuesToReturn.

  6. The following example demonstrates the failure scenario:

    public class StudentDaoTest {
    
      private TestableStudentDao dao;
      private Map<String, Integer> sqlCount = null;
      @Before
      public void setup() {
        dao = new TestableStudentDao();
        sqlCount = new HashMap<String, Integer>();
      }
    
      @Test(expected=IllegalStateException.class)
      public void when_row_count_does_not_match_then_rollbacks_tarnsaction(){
      List<Student>  students = new ArrayList<>();
      students.add(new Student(null, "Gautam Kohli"));
    
      int[] expect_update_fails_count = {0};
      dao.valuesToReturn = expect_update_fails_count;
      dao.batchUpdate(students);
    
    }
  7. Check whether dao is instantiated with TestableStudentDao, then a new student object is created, and the valuesToReturn attribute of the fake object is set to {0}. In turn, the batchUpdate method will call the update method of TestableStudentDao, and this will return a database update count of 0. The batchUpdate() method will throw an exception for a count mismatch.

    The following example demonstrates the new Student creation scenario:

    @Test
    public void when_new_student_then_creates_student(){
      List<Student>  students = new ArrayList<>();
      students.add(new Student(null, "Gautam Kohli"));
    
      int[] expect_update_success = {1};
      dao.valuesToReturn = expect_update_success;
      dao.batchUpdate(students);
    
      int actualInsertCount = sqlCount.get("insert");
      int expectedInsertCount = 1;
      assertEquals(expectedInsertCount, actualInsertCount);
    }

    Note that the valuesToReturn array is set to {1} and the Student object is created with a null roleNumber attribute.

  8. The following example demonstrates the Student information update scenario:

      @Test
      public void when_existing_student_then_updates_student_successfully(){
        List<Student> students = new ArrayList<>();
        students.add(new Student("001", "Mark Leo"));
        int[] expect_update_success = {1};
        dao.valuesToReturn = expect_update_success;
    
        dao.batchUpdate(students);
        int actualUpdateCount = sqlCount.get("update");
        int expectedUpdate = 1;
        assertEquals(expectedUpdate, actualUpdateCount);
      }

    Note that the valuesToReturn array is set to {1} and the Student object is created with a roleNumber attribute.

  9. The following example unit tests the create and update scenarios together. We will pass two students: one to update and one to create. So, update should return {1,1} for the existing students and {1} for the new student.

    We cannot set this conditional value to the valuesToReturn array. We need to change the update method's logic to conditionally return the count, but we cannot break the existing tests. So, we'll check whether the valuesToReturn array is not null and then return valuesToReturn; otherwise, we will apply our new logic.

    The following code snippet represents the conditional count logic:

    class TestableStudentDao extends StudentDaoImpl {
      int[] valuesToReturn;
      int[] update(String sql, List<Map<String, Object>> params) {
    
        Integer count = sqlCount.get(sql);
        if(count == null){
          sqlCount.put(sql, params.size());
        }else{
          sqlCount.put(sql, count+params.size());
        }
    
    
        if (valuesToReturn != null) {
          return valuesToReturn;
        }
    
        int[] val = new int[params.size()];
        for (int i = 0; i < params.size(); i++) {
          val[i] = 1;
        }
    
        return val;
      }
    }

    When valuesToReturn is null, the update method creates an array of the params size and sets it as 1 for each index. So, when the update will be called with two students, the update method will return {1,1}.

    The following test creates a student list of three students, two existing students with roleNumbers and one new student.

    @Test
    public void when_new_and_existing_students_then_creates_and_updates_students() {
      List<Student> students = new ArrayList<>();
      students.add(new Student("001", "Mark Joffe"));
      students.add(new Student(null, "John Villare"));
      students.add(new Student("002", "Maria Rubinho"));
    
      dao.batchUpdate(students);
    
    }

    The following screenshot shows the output of the JUnit execution:

Note

Note that it took 0.041 seconds to execute four tests. This is interesting because it's something that you wouldn't easily get if you were using a real database.

 

Summary


This chapter covered the concept of automated unit tests, the characteristics of a good unit test, and explored tests doubles. It provided the examples of dummy objects, fake objects, stubs, mock objects, and spies.

By now, you will be able to identify the different test doubles and write unit tests using test doubles.

The next chapter, Socializing with Mockito, will focus on getting the reader quickly started with the Mockito framework.

About the Author
  • Sujoy Acharya

    Sujoy Acharya works as a Principal Engineer with Cerner. While growing up, he pursued his interests in the fields of computer science and engineering. His hobbies are watching movies and sitcoms, playing outdoor sports, and reading books.

    Sujoy likes to research upcoming technologies. His major contributions are in the fields of TDD, building scalable applications, cloud services, and the Spring Framework.

    He has authored four books for Packt, namely, Test-Driven Development with Mockito, Mastering Unit Testing using Mockito and JUnit, Mockito Essentials, and Mockito for Spring.

    Browse publications by this author
Latest Reviews (1 reviews total)
Mockito Essentials
Unlock this book and the full library FREE for 7 days
Start now