Book Image

Mockito Essentials

By : Sujoy Acharya
Book Image

Mockito Essentials

By: Sujoy Acharya

Overview of this book

Table of Contents (14 chapters)

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.