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.
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.
- Download the source code for this sample app from git.
- Do maven clean install, deploy it on your favorite server.
- Hit this url in the browser http://localhost:8080/mockService/welcome
- You should see the users list.
- To get all users do http://localhost:8080/mockService/1.0/users
- Set the header to Accept: application/json or Accept: application/xml,
- API should respond with same users list.
- To add user, go to http://localhost:8080/mockService/1.0/users/
- set the request type as post and set the payload like below,
- {"id": "104","firstName": "Vin4","lastName": "Shiv4","email": "vin4@email.com","phone": "423-456-7890"}
- You could update the user http://localhost:8080/mockService/1.0/users/104
- {"id": "104","firstName": "Vin444","lastName": "Shiv444","email": "vin444@email.com","phone": "423-444-7890"}
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.
- 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