Core Java

Built-in Fake Objects

While mock objects are perfect instruments for unit testing, mocking through mock frameworks may turn your unit tests into an unmaintainable mess.

The root cause of this complexity is that our objects are too big. They have many methods and these methods return other objects, which also have methods. When we pass a mock version of such an object as a parameter, we should make sure that all of its methods return valid objects.

This leads to inevitable complexity, which turns unit tests to waste almost impossible to maintain.
 

Object Hierarchy

Take the Region interface from jcabi-dynamo as an example (this snippet and all others in this article are simplified, for the sake of brevity):

public interface Region {
  Table table(String name);
}

Its table() method returns an instance of the Table interface, which has its own methods:

public interface Table {
  Frame frame();
  Item put(Attributes attrs);
  Region region();
}

Interface Frame, returned by the frame() method, also has its own methods. And so on. In order to create a properly mocked instance of interface Region, one would normally create a dozen other mock objects. With Mockito it will look like this:

public void testMe() {
  // many more lines here...
  Frame frame = Mockito.mock(Frame.class);
  Mockito.doReturn(...).when(frame).iterator();
  Table table = Mockito.mock(Table.class);
  Mockito.doReturn(frame).when(table).frame();
  Region region = Mockito.mock(Region.class);
  Mockito.doReturn(table).when(region).table(Mockito.anyString());
}

And all of this is just a scaffolding before the actual testing.

Sample Use Case

Let’s say, you’re developing a project that uses jcabi-dynamo for managing data in DynamoDB. Your class may look similar to this:

public class Employee {
  private final String name;
  private final Region region;
  public Employee(String empl, Region dynamo) {
    this.name = empl;
    this.region = dynamo;
  }
  public Integer salary() {
    return Integer.parseInt(
      this.region
        .table("employees")
        .frame()
        .where("name", this.name)
        .iterator()
        .next()
        .get("salary")
        .getN()
    );
  }
}

You can imagine how difficult it will be to unit test this class, using Mockito, for example. First, we have to mock the Region interface. Then, we have to mock a Table interface and make sure it is returned by the table() method. Then, we have to mock a Frame interface, etc.

The unit test will be much longer than the class itself. Besides that, its real purpose, which is to test the retrieval of an employee’s salary, will not be obvious to the reader.

Moreover, when we need to test a similar method of a similar class, we will need to restart this mocking from scratch. Again, multiple lines of code, which will look very similar to what we have already written.

Fake Classes

The solution is to create fake classes and ship them together with real classes. This is what jcabi-dynamo is doing. Just look at its JavaDoc. There is a package called com.jcabi.dynamo.mock that contains only fake classes, suitable only for unit testing.

Even though their sole purpose is to optimize unit testing, we ship them together with production code, in the same JAR package.

This is what a test will look like, when a fake class MkRegion is used:

public class EmployeeTest {
  public void canFetchSalaryFromDynamoDb() {
    Region region = new MkRegion(
      new H2Data().with(
        "employees", new String[] {"name"},
        new String[] {"salary"}
      )
    );
    region.table("employees").put(
      new Attributes()
        .with("name", "Jeff")
        .with("salary", new AttributeValue().withN(50000))
    );
    Employee emp = new Employee("Jeff", region);
    assertThat(emp.salary(), equalTo(50000))
  }
}

This test looks obvious to me. First, we create a fake DynamoDB region, which works on top of H2Data storage (in-memory H2 database). The storage will be ready for a single employees table with a hash key name and a single salary attribute.

Then, we put a record into the table, with a hash Jeff and a salary 50000.

Finally, we create an instance of class Employee and check how it fetches the salary from DynamoDB.

I’m currently doing the same thing in almost every open source library I’m working with. I’m creating a collection of fake classes, that simplify testing inside the library and for its users.

Related Posts

You may also find these posts interesting:

Reference: Built-in Fake Objects from our JCG partner Yegor Bugayenko at the About Programming blog.

Yegor Bugayenko

Yegor Bugayenko is an Oracle certified Java architect, CEO of Zerocracy, author of Elegant Objects book series about object-oriented programing, lead architect and founder of Cactoos, Takes, Rultor and Jcabi, and a big fan of test automation.
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
Martin Vanek
Martin Vanek
9 years ago

I strongly agree. Unit tests often look like showcase of “101 crazy things you can do with Mockito” so most of the test code is mocking rather then assertions and validation checks.
At the end it is quite harmful because it makes refactoring more difficult.

Back to top button