Developing Mobile App Without Server
Mobile developers often get in a situation where the app’s backend doesn’t work or it doesn’t provide requested methods. Maybe it’s the same thing with web applications.
This can happen for various reasons. Most of the time backend is simply not ready by the start of the project, and the client begins development without it. In this case the start of development gets delayed by two or four months.
It could be that the server went down or it fails to deploy requested methods. Something might have gone wrong with the data etc. All these issues led us to create a service called Mocker that allows us to replace real backend.
How I came up with this
My first year at the company was coming to an end when I got assigned to a new e-commerce project. The manager said we had four months to complete the project but the client’s backend team would only start development in in six weeks. In the meantime we were supposed to come up with a load of UI features.
I suggested writing a mock back- end. Before I became an iOS developer I used to play around with .NET at university. The idea was quite simple: we had to write method stubs within given specification that would take data from pre-prepared JSON-files. And that was it.
Shortly after I took a vacation. “Why not generate all this automatically?”, I thought. During my vacation I wrote an interpreter of a kind that takes the APIBlueprint specification and generates .NET Web App (C# code).
Thus, the first version of this tool appeared, and we’d been using it for about two and a half months. I can’t give you exact numbers showing how much it helped us. But I remember how someone said at retrospective that if it weren’t for this, there would be no release whatsoever.
A few years later I worked through the numerous mistakes I initially made and completely rewrote the tool.
Here I would like to thank my colleagues who helped with their feedback and advice. Also a big thank you to the managers who tolerated all the “engineering mayhem”.
Introduction
As a rule, a client-server application looks something like this:
Each screen contains at least one request (and often more). As we navigate downwards between screens, we need to make more and more requests. Sometimes we can’t even make the transition until the server tells us “Show the button”. That is, a mobile app is highly dependent on the server, not only during its immediate operation but also at the development stage. Let’s take a look at an abstract product development cycle:
- First off, we design the app. Decompose, describe and discuss it.
- After we have received the tasks and requirements, we begin development. We write code, make layout etc.
- Following the implementation of some functionality, we make a build for manual testing to run various test cases.
- If everything is in order and testers approve the build, it goes straight to the client.
Each process is essential. Particularly presentation, since the client needs to know what stage we are at. Sometimes they have to report to their management or investors. Generally such reports are organized as a showcase for the mobile app. There was a case in my practice when the client demonstrated literally half of the MVP that was working on mocks only. An app with mocks looks like a duck and it quacks like one, meaning it’s real.
However, it’s just a golden dream. Let’s see what really happens if we don’t use a server
- The development process is going to be slower and more challenging since we can neither write services properly or test all cases. We have to create stubs that we will have to delete later.
- After we have managed to make the build, it goes to testers who can’t puzzle out what to do with it. There’s nothing to test, half the app doesn’t work at all because there is no server. Hence, they miss plenty of bugs, both logical and visual.
- Well, we still had to send the build to the client, and this is when it got ugly. The client couldn’t properly evaluate our work, all he saw was one or two out of all possible cases. Naturally, he couldn’t show this to the investors.
Everything went sideways from there. Unfortunately, situations like this happen all the time. Sometimes we get by without servers for two months, sometimes six, for various reasons. The server may be slow or you may need to quickly test boundary cases that you can’t recreate on a real server.
For instance, we want to test the app’s behavior if the user’s payment takes longer than it should. Recreating such a case on a server is highly difficult and time-consuming, and you have to do this artificially.
Summing up, we have following issues:
- Server is completely lacking. This makes it impossible to develop, test, and present the app.
- Server is falling behind, which hinders development and may interfere with testing.
- We want to test boundary cases but it takes a lot of time and effort to make the server work.
- Lack of server affects testing and jeopardizes presentation.
- Server crashes. Once we lost a server for three days during a sustained development streak.
We created Mocker to solve these problems.
Working principle
Mocker is a small web service that listens to traffic on a particular port and can respond to specific network requests with prepared data.
The sequence looks like this:
1. Client sends a request.
2. Mocker receives the request.
3. Mocker finds the desired mock.
4. Mocker returns the mock.
Steps 1, 2 and 4 are clear enough but step 3 raises some questions.
In order to see how the service finds the desired mock let’s take a look at the mock’s structure.
A mock is a file with JSON in the following format:
Let’s analyze each field:
url
This parameter is used to set request URL that the client is accessing.
For instance, if the mobile app is requesting url host.dom/path/to/endpoint, we have to write /path/to/endpoint in the url field. Thus, the field stores relative path to the endpoint.
This field must be in url-template format. The following formats are allowed:
- /path/to/endpoint — a common url address. As the program receives a request, it compares the lines by character.
- /path/to/endpoint/{number} — url with a path pattern. A mock using this URL responds to any request that meets this pattern.
- /path/to/endpoint/data?param={value} — url with a parameter pattern. A mock with such url will respond to a request containing specified parameters. However, if one of the parameters in the request is lacking, it won’t match the pattern.
By controlling URL parameters you can clearly determine when a specific mock returns to a specific url.
method
An http method, e.g. POST or GET.
The line must only contain capital letters.
statusCode
An http response status code. By requesting this mock client receives a response with status shown in the statusCode field.
response
This field contains a JSON object which is sent to client in response body.
request
Here we put the request body that we received from client. We use it to give the corresponding response depending on the request body. For example, if we want to change responses depending on request parameters.
If client sends a request with body:
{ “login”: “Tester”, “password”: “Valid”}
Here is what the response will look like:
{ “token”: “cbshbg52rebfzdghj123dsfsfasd”}
If we want to check the app’s behavior in case password is incorrect, a request with body will be sent:
{ “login”: “Tester”, “password”: “Invalid”}
Here is what the response will look like:
{ “message”: “Bad credentials”}
This allows us to test password failure, as well as all the other cases.
Now, let’s see how mock search and grouping work.
In order to make mock search more efficient, server loads all mocks and groups them in an appropriate way. You can find the example of grouping in the picture above.
The server combines different mocks by url and method. It is necessary among other things for us to be able to create one url for several different mocks.
Imagine that we want to get different responses and change screen state on Pull-To-Refresh in order to test all boundary cases.
We can create a lot of different mocks with the same method and url parameters while the server will return them in turns.
Let’s take these mocks as an example:
Then, if we call the GET /products method for the first time, this is the first response we will get:
When we call it for the second time, the iterator will move to the next element and this is what will return:
We can also check app’s behavior in a case where we get big values and so on.
When we get to the last item and call the method once more, the first item will return again because iterator will go back to the first item.
Caching proxy
Mocker can work in a caching proxy mode. This means that when the service gets a request from the client, it retrieves host address of a real server and a scheme for protocol identification. Next, the service takes the request and service information (host and scheme) and sends a response to a real server. All request headers are included, so if method requires authentication, Authorization: Bearer … will be transferred.
After receiving a response with a 200 status code Mocker saves it to a mock file and returns data received from the real server to the client. You can copy or change this mock file later. Moreover, it doesn’t just save the file to a random location but organizes the files in a way that you can work with them manually later. As an example, Mocker sends a request to this URL: hostname.dom/main/products/loans/info. It will then create a folder called hostname.dom. Inside of it lies a folder called main, inside of main there will be a folder called products and so on.
In order to avoid duplicating mocks the title is based on an http-method (GET, PUT…) and real server’s response body hash. If there already exists a mock for a particular response, it will simply be overwritten.
This feature can be activated individually for each request. To do this, add these headers to the request:
X-Mocker-Redirect-Is-On: “true”,X-Mocker-Redirect-Host: “hostaname.ex:1234”,X-Mocker-Redirect-Scheme: “http”
Specifying path to mocks explicitly
Sometimes we want Mocker to return only the mocks that we need, not each and every one existing in the project.
This is particularly useful for testers. It would be convenient for them to have some prepared set of mocks for each of the test cases. Then, during the test, a QA could simply choose the folder they need and work in piece, not bothered by the cacophony of unrelated mocks.
This is now possible. To activate this function, use a special header:
X-Mocker-Specific-Path: path
Let’s take this Mocker directory structure as an example:
root/ block_card_test_case/ mocks…. main_test_case/ blocked_test_case/ mocks…
If you need to run a test case for blocked cards, write this:
X-Mocker-Specific-Path: block_card_test_case
If you need to test for main screen lock, use this:
X-Mocker-Specific-Path: main_test_case/blocked_test_case
Interface
At first we would work with mocks directly through ssh. As the number of mocks and users grew, we switched to a more convenient option. We are currently using CloudCommander. In the docker-compose example it connects directly with the Mocker container.
Here is what it looks like:
You get a web editor as a bonus, which allows you to add or change mocks straight from a browser.
This is also a temporary solution. We are planning to switch from using mocks via file system to some database. This way mocks could be managed from the GUI of this database.
Deployment
Using the Docker tool is the easiest way to deploy Mocker. On top of that, if we deploy the service from Docker, a web interface will appear automatically. This makes working with mocks considerably easier. Files needed for deployment via Docker are located in the repository.
However, if this option doesn’t suit you, you can build this service yourself using the sources. Here is what you need to do:
git clone https://github.com/LastSprint/mocker.gitcd mockergo build .
Then you need to write a configuration file (an example) and run the service:
mocker config.json
Known issues
You need to call curl mockerhost.dom/update_models after adding a new file in order for the service to read them. I have yet to find a quicker and easier way to update the service. You can encounter bugs in CloudCommander that don’t let you edit mocks created using the web interface. This can be solved by clearing the browser cache. The service only works with application/json now. We also plan to support form-url-encoding.
Conclusion
Mocker is a web service that helps in solving issues of client-server app development when the server is not ready for some reason.
This service allows you to create various requests for a single URL and link Request and Response. It can be achieved either by putting explicit parameters in the url or directly by setting up the request body. The service has a web interface that makes it user friendly.
A user can add a desired endpoint or a request they need. If you need to switch to a real server, all you have to do is replace the constant with the host address.
I hope that this article will be useful to people who encountered similar issues. Perhaps you too will contribute to this tool’s development.