This is the third part of our series Growing Android Applications Guided by Tests. We’ve already covered The Setup in part I, and we’ve built A Walking Skeleton in part II. Now we’re here to mock the API service.
To parse a search repository response into Java models (see code here) we decided to use Jackson as a Json parsing library for its performance, flexibility and annotation-based configuration. To ensure maximum flexibility, we hide the ObjectMapper object with our own implementation, JsonConverter. We use custom converters for every entity, so that the object creation must pass through the constructor. This way our models remain immutable and we can enforce class invariants such as "startDate < endDate". This also keeps Jackson as an implementation detail and we're free to swap for another library (like Gson) without touching our entities. We make sure that entities that are Collections are treated as first class objects, because they represent a concept in our domain and may contain specific business logic.
Right now our failing test is asking us to present repositories on our UI. Funny thing is: we don’t even have a
Repository object, so we should start writing some entity models. What should a
Repository object look like? Well, since we are only displaying its description in our UI, it is fine to only contain a description field for now.
But what about the concept “A list of repositories”? Should we represent it with a
List<Repository> type? We strongly disagree. In fact, our idea is that
Collections should be first class citizens. This is one of the Object Calisthenics Rules that we try to follow when we’re designing code, and we follow it because of several advantages. First, an object called
Repositories may contain business logic relative to its element that a
List<Repository> cannot. For example, it can have a method
areFromSameAuthor or even a
groupByAuthor that splits this list into various sublists. If you're passing an encapsulated
Repositories object around your application you're hiding its status inside the class, preventing details from leaking all over the architecture. Finally,
Repositories is much easier to read and write than
Until we associate behaviour with these classes it is pointless to write entities test-first as they are just a bag of objects but we can make a start by writing tests for our Json conversion tools. We can define our own
JsonConverter class which abstracts from the real library that we will use for the conversion (either Gson or Jackson since these are the two competing standards). We exercise this converter with some sample responses to check that the mapping works smoothly.
If you haven't already, take a second to look at the source code in the entity package. Notice that this commit contains a fully functional conversion framework, but do you see any trace of this Json parsing mechanism in those classes? That's exactly what we were aiming for: all the conversion details are hidden in a separate package, and none of these details leaks into our entity package.
Many developers when using
Gson are tempted either to overload their classes with annotations or to use specific names for fields and accessor methods. We're working hard to resist this temptation because it will create a tight coupling between the conversion tool and our model objects. If you are a seasoned developer you will know that the more you loosely couple your modules, the more flexiblility you will retain within your application and the easier it will be to embrace change.
This is particularly critical when you're dealing with third party services and APIs. It is likely that the Json returned from the service will not be well suited for your application and you need a place to add customisations and convert the response into a meaningful object for your domain. Even if you completely trust the external service, it is always a good idea to add a separate layer whose responsibility is to take care of the conversion.
The received Json may contain badly named variables, inconsistencies and other funny oddities. If you have found yourself casting
Dates inside your business logic, you know what I mean. Dealing with these problems inside your models will only add noise and distract you each time you open these classes within your IDE. Since these models are going to be integral to your development and tesing, it is better to keep them in lean and be very strict in applying the Single Responsibility Principle. Remember: as a developer you will spend 5 more times reading code than actually writing it.
If you apply the conversion via annotations or accessors you will be giving up one of the most powerful tools at your disposal: enforcing constraints. Since you're not in control of what you are receiving, you should mandate a centralized point from where you can sanitise the input and apply some logic in order to fail fast if necessary. A
Constructor is the ideal place where to apply any logic, and you can't use it if you create the object via reflection. If your
Invoice object has an invalid date, you may not going to find this problem until it's too late to recover, or to throw a meaningful error.
As you can see from the source code, if you are using
Jackson's custom converters and builders, you can externalise the parsing framework with little overhead, keeping your application clean, your model objects small and enforcing their creation through the constructor. If that isn't cool enough, you can bask in the satisfying glow of knowing that at any moment you could swap to another parsing library such as
Gson. All you would need to touch was one small package, leaving the rest of your application unaltered. That's ultimate flexibility!
We’re writing an implementation for the GithubApi interface that will provide predictable responses for every query. That way we can run the entire application without having to rely on the Github servers, speeding up both testing and development. Also, those mocked responses allow us to eliminate some duplication in the test classes.
When I started this series of posts, I stressed the importance of having stubbed implementations for each third party service you are using within your application. In this commit you can see how easy it is to now create stub implementations with a conversion framework in place.
Your stub service will always return the same Json response for a certain subset of requests and predictable responses while we're writing test fixtures. As a rule of thumb, it is sufficient to provide at least two different responses for a certain API request: one coming from a local file, and the other coming from in-memory Java objects. The reason is that you want some in-memory objects so that you can reference them in your tests (look at the
MockSearchRepositories usages, for example), while the file-cached response simulatates the normal behaviour of the service. Two responses are really the minimum viable implementation but you can have as many local Json files as you like.
We plan, design, and develop the world’s most desirable Android products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.
Let’s get in contact