Thursday, April 10, 2014

Mocking Rest API Simplified


              Is your web development in jeopardy due to unavailable services that it would intend to consume but are not available? We are in the era of micro services where it’s common that your web requires making lot of calls to web services. There is a danger of overlapping development life cycles of web service components. Quite naturally this might add pressure on development and would cripple due to the unavailability of services to consume or changes in service without notice. This is where we think of mocking these services. That’s right, let’s mock these services quickly and move on with web development. Now that you have decided to mock, you would start researching with a hope that there must be some frameworks out there already that can do this job. But unfortunately it’s not really the case and soon you would tell yourself, “Let me build one simple framework to do this”.

             There are third party mock tools that are available as hosted services or cloud services as well few tools available that you can host on in premises.
From the development perspective mocking can be achieved either on service side or on web side. If you are using Angular or Ember JS frameworks for the web then you are in luck as they both support mocking framework. But if you plan to mock your services rather then you have few options SOAP UI, Mocky, Mockable, WireMock etc. 
I don’t want to get into comparing these tools in this article, I liked wiremock but some caveats to it and I will get to it in another article. I guess you will have to shift your mindset to go with one of these tools to mock your services. Bottom line you know the idea of mocking, it should not complicate the development process and switching between the mocked and real ones should be trivial. Anyways if you are looking for some simple lightweight framework to achieve this then here is one thought!

First Thing First

             Let’s get to the details of developing a framework within your application to mock services. All you need is the Jackson library to build this framework. You setup, develop and publish your Rest Services as usual but you only mock the responses. Mocking a service is as simple as, reading the mock data from a file and building the response dynamically. You want to mock the data in XML or Json, either way Jackson got you covered.
Mocking is a simple 2 step process,
  • Step 1 is recording, you use your junit test case to compose objects needed to produce the response and record the response into a file. The response could be either in XML or JSON.
  • Step 2 is time to playback, write a Mock repository object to read the response from these files, convert to objects and pass it back to your service.

Ha, first thing is to run the app. 
Now open the rest client of your choice

 Mock It Away!

          Let’s look into details of this simple Rest API to support Users for a company that supports adding, updating, retrieving users.

Simple user object,

public class User implements Serializable {
    private String id;
    private String firstName;
    private String lastName;
    private String email;
    private String phone;
}
UserResponse object in this case simply wraps User object,
public class UserResponse implements Serializable{
    private List<User> users = new ArrayList<User>();
}
Mocking user data should be trivial, create users.xml and place it in src/main/resources directory



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Users>
    <User>
        <id>101</id>
        <firstName>Vin1</firstName>
        <lastName>Shiv1</lastName>
        <email>vin1@email.com</email>
        <phone>123-456-7890</phone>
    </User>
    <User>
        <id>102</id>
        <firstName>Vin2</firstName>
        <lastName>Shiv2</lastName>
        <email>vin2@email.com</email>
        <phone>223-456-7890</phone>
    </User>
    <User>
        <id>103</id>
        <firstName>Vin3</firstName>
        <lastName>Shiv3</lastName>
        <email>vin3@email.com</email>
        <phone>323-456-7890</phone>
    </User>
</Users>
Interested only in JSON, create users.json

{"users":
 [{"id":"101","firstName":"Vin1","lastName":"Shiv1","email":"vin1@email.com","phone":"123-456-7890"},
 {"id":"102","firstName":"Vin2","lastName":"Shiv2","email":"vin2@email.com","phone":"223-456-7890"},
  {"id":"103","firstName":"Vin3","lastName":"Shiv3","email":"vin3@email.com","phone":"323-456-7890"}]
}


This sample includes 2 Mock Repository classes, let’s look at our MockXmlUserRepository.java


@Repository
public class MockXmlUserRepository {

    private static final String USERS_XML_FILE = "users.xml";
    private static final String FILE_DIR = "c:/sandbox/mockService/src/main/resources/mockdata/";
    
    @Cacheable(value = { "usersCache" })
    public UserResponse getUsers() throws Exception {
 UserResponse userResponse = loadUsers();
 return userResponse;
    }

    private UserResponse loadUsers() throws Exception {
 JAXBContext context = JAXBContext.newInstance(UserResponse.class);
 Unmarshaller un = context.createUnmarshaller();
 return (UserResponse) un.unmarshal(new File(FILE_DIR + USERS_XML_FILE));
    }
}


As you see a JAXBContext unmarshalling the XML data from file to Users object. If you look at the MockJsonUserRepository.java


@Repository
public class MockJsonUserRepository {

    private static final String USERS_JSON_FILE = "users.json";
    private static final String FILE_DIR = "c:/sandbox/mockService/src/main/resources/mockdata/";

    @Cacheable(value = { "usersCache" })
    public UserResponse getUsers() throws Exception {
 UserResponse userResponse = loadUsers();
 return userResponse;
    }

    private UserResponse loadUsers() throws Exception {
 return (UserResponse) new com.fasterxml.jackson.databind.ObjectMapper()
  .readValue(new File(FILE_DIR + USERS_JSON_FILE),
   UserResponse.class);
    }
}


We use Jackson’s mapper object to read json and convert to java object.
UserService implementation uses Repository to get those response user objects built earlier,


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private MockXmlUserRepository mockXmlUserRepository;

    @Autowired
    private MockJsonUserRepository mockJsonUserRepository;
    
    private Cache usersCache = null;

    @SuppressWarnings("unchecked")
    @Override
    public UserResponse getUsers() throws Exception {
 ConcurrentMap<String, User> map = (ConcurrentMap<String, User>) usersCache.getNativeCache();
 UserResponse userResponse = new UserResponse();
 List<User> userList = new ArrayList<User>(map.values());
 userResponse.setUsers(userList);
 return userResponse;
    }

    @Override
    public User getUserById(String id) {
 return usersCache.get(id, User.class);
    }

    @Override
    public void addUser(User user) {
 usersCache.put(user.getId(), user);
    }

    @Override
    public void updateUser(final String id, User user) {
 usersCache.evict(id);
 usersCache.put(id, user);
    }    
}


Two Mock Repository objects were injected into the service. We are using spring simple cache manager to manage these objects once loaded by repository.
All our service methods are simply using this cache to compose the response.
We want to load these mock data response objects at the application startup so we will use bean initializer init method by using @PostConstruct annotation like below,


@PostConstruct
    public void init() throws Exception {
 loadUsersCache();
    }
private Cache loadUsersCache() throws Exception {
 if (usersCache != null)
     return usersCache;
 usersCache = new ConcurrentMapCache("usersCache");
 UserResponse userResponse = mockXmlUserRepository.getUsers();
 if (userResponse != null && userResponse.getUsers() != null) {
     for (User user : userResponse.getUsers())
  usersCache.putIfAbsent(user.getId(), user);
 }
 return usersCache;
    }


Note that loadUsersCache method is using Xml Mock Repository but you could switch to json repository if needed as it’s already auto wired.
Finally the Rest controller to return users by calling the service above,


@RequestMapping(method = RequestMethod.GET, headers = { "Accept=application/json,application/xml" }, produces = {
     "application/json", "application/xml" })
    @ResponseBody
    public Users getUsers(HttpServletRequest request) throws Exception {
 Users users = userService.getUsers();

 return users;
    }


Notice that this service supports both XML and Json response. Download the sample app for complete listing of this controller.
Each service or domain will have its own Mock Repository and could ideally be replaced with real implementation whenever it’s ready.
So at this point your service is mocked and ready to serve the request.
  
You must be thinking, does that mean I need to hand write these xml/json files? What if my objects structure is complicated? Is there a better way? But wait, where is the step 1 that you mentioned earlier?
Here is step one, this is a simple process to produce these mock data files through your Junit. Look at Junit testcase to our Mock Repository classes that not only test the unmarshaller but also marshall’s the data and writes to a file. There is recordUsers and playUsers method that does all this.


    @Test
    public void recordUsers() throws Exception {
 usersToXML(buildUsers());
    }
    
    @Test
    public void playUsers() throws Exception {
 UserResponse users = xmlToUsers();
 assertEquals(3, users.getUsers().size());
    }


buildUsers() is a test harness to compose the data objects.


private UserResponse buildUsers(){
 UserResponse userResponse = new UserResponse();
 userResponse.getUsers().add(buildUser("101", "Vin1", "Shiv1", "vin1@email.com", "123-456-7890"));
 userResponse.getUsers().add(buildUser("102", "Vin2", "Shiv2", "vin2@email.com", "223-456-7890"));
 userResponse.getUsers().add(buildUser("103", "Vin3", "Shiv3", "vin3@email.com", "323-456-7890"));
 return userResponse;
    }


recordUsers() calls usersToXML method that marshall’s the UserResponse object parameter into a xml file under src/main/resources directory.

private void usersToXML(UserResponse userResponse) {
 try {
            JAXBContext context = JAXBContext.newInstance(UserResponse.class);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            m.marshal(userResponse, new File(FILE_DIR+USERS_XML_FILE));
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }


playUsers() calls xmlToUsers() that does unmarshalling the data from a file.


private UserResponse xmlToUsers() {
        try {
            JAXBContext context = JAXBContext.newInstance(UserResponse.class);
            Unmarshaller un = context.createUnmarshaller();
            UserResponse userResponse = (UserResponse) un.unmarshal(new File(FILE_DIR+USERS_XML_FILE));
            return userResponse;
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }


You need to write the test harness to build the test data objects. Once you got the file generated it’s easier to manipulate the data in a file later.

Final Note

There are some pros and cons around this approach and here are some that I can think of.

Advantages

  • You will be able to work through your object structure to compose the Json/Xml response otherwise it’s hard to compose json response in case of complex object structure.
  • Allows you to manipulate the mock data in a generated mock file to mock large set of data easily if needed.
  • Allows you to publish a real service by having you exercise through all the setup, environment and configuration issues.
  •  Allows you to defer implementation details to the later stage by mocking the response.
  • The implementation could be as simple as switching to real repository objects when there are ready.
Disadvantages

  • You will have to go through complete set up, environment and configuration steps as this is almost a real service.
  • You will have to go through deployment process every time requirement changes as you will have to adjust the response data.
  • Please feel free to share your thoughts on this approach or got better ideas to achieve this.
That’s it for now, happy mocking.

No comments:

Post a Comment