What happens to your web application if one of the static libraries takes 10 seconds to load instead of 100ms? Does it stay blank? Does it crash? Does it show the page but then crashes when the user tries to interact with it?
What happens if the server responds with a 500 error code to an Ajax request? Do you silently swallow the error, and the user keeps waiting in vain? Does the web application crash? Or does it show a meaningful error message to the user? Is the error sent to the crash reporting service, like Sentry?
I consider the web sites that graciously handle run time errors like an occasional 500 server response to be well-built and trustworthy ones. It is a good sign that the team not only has finished working on the regular features but has also properly designed and tested the error handling code paths.
From my personal development experience testing all possible intermittent failures is very hard and time consuming. It is very difficult to simulate an occasional 500 error on demand - the feature testing against the staging or production environments assumes the servers are a black box. Adding the ability to simulate the server's failures will surely add unnecessary complexity to the server's code. It also does not allow simulating network delays and responses with an unexpected data formats (two things very common in the real world).
I have designed and coded 3 different types of proxies for simulating these specific types of failures. The proxies can be used without any or with minimal application code modifications and allow simulating server failures, network delays and returning data in an unexpected format. These proxies are:
- turtle-run is a stand alone proxy that runs between the tester and server. It can slow down some requests, or return a specific status code or mock data based on url match.
- service-turtle is a proxy that runs inside the latest Chrome browser using ServiceWorker technology. It can intercept any HTTP request and return a fake status, and even delay the mock response by a given time period.
- ng-wedge is the last script that can modify the web application's code on the fly. It is the most flexible approach that does not require an intermediate proxy server or an experimental browser API. Unfortunately this approach assumes the tested application uses AngularJS framework and requires good development insight into the application's code. In my opinion it is most useful to the developer who is working on the application.
I will show how these proxies can be used to test the web application's robustness to the common errors using a small AngularJS application. The source code is available at bahmutov/github-user and the live demo is available at glebbahmutov.com/github-user.
You can follow the application's testing by cloning the repo and starting with tag step-0
git clone [email protected]:bahmutov/github-user.git
cd github-user
git checkout step-0
I recommend that you use a static http server to run the web application, for example http-server
cd github-user
sudo npm install -g http-server
http-server -p 3003
// open localhost:3003 in your browser
The application should run in the local browser at localhost:3003
. If you enter a github username and click
"Load user info" there is separate Ajax request to https://api.github.com
. The results should be displayed
below the input box.
Testing against network delays using turtle-run
The first thing we want to test is how the application looks when a 3rd party library does not load at all or
loads only after a delay. The github-user
demo page requires only a single external script to load,
and it is included with the repo itself.
1 | <head> |
What happens if the angular.js
file does not load? Does the page stay blank? Usually we load the script
angular.js
from a CDN, so it is likely that it will ultimately load, but there might be a significant delay.
Let us simulate the delay in loading angular.js
using a proxy and see how the web page looks
while the request is pending.
Install the turtle-run
proxy and create a JSON file describing which request to delay. I created sample
file and tagged the commit step-1
sudo npm install -g turtle-run
git checkout step-1
The turtles.json
is very simple. It tells the proxy where to forward requests to, and for each matching
url pattern what to return / delay amount. In this case angular.js
script will be loaded after 5 seconds.
1 | { |
While the local http-server
is running at port 3003, start the turtle-run
and pass it the turtle
config json filename. We can start in the github-user
folder using the default turtles.json
filename
cd github-user
http-server -p 3003 &
turtle-run
Open the browser localhost:8008
.
GET /
GET /bower_components/angular/angular.js
Connect-slow: delaying 5000 ms on url /bower_components/angular/angular.js
The turtle-run
sits between the local server and the user's browser, intercepting every request
and deciding how to proceed. Most of the requests are just forwarded to the target. The requests matching
the /angular\.js/
regular expression are forwarded to the target server after 5 seconds. Note: the Ajax request
to the Github API is NOT proxied by the turtle-run
because it is a direct cross-domain request
from the page to the Github API endpoint.
Notice that the page stays blank for 5 seconds before anything appears!
This is because the proxy slows down bower_components/angular/angular.js
script, returning the
script after 5 seconds. The script is included in the head
element and is blocking the rest of the page
from loading.
We can solve this problem by moving the script to the bottom of the page (or by adding async
attribute).
After moving the angular.js
to the bottom of the body
tag, the page loads right away, despite the delay.
Unfortunately, this shows a few new problems.
We can hide the application until it is ready to interact with the user using ng-cloak directive.
To make the directive work, we include the angular CSS file in the head
element
(this small file will be blocking load), and then apply the cloak directive to the angular part of the page.
1 | <head> |
Now even if the angular.js
takes a long time to load, the user will see the initial page content.
Then the Angular application will reveal itself. You can see the changed code at tag step-2
.
Using turtle-run
one can make the application more robust to network and server errors.
Read Give browser a chance for other suggestions
how to work around the delayed or missing page information.
Intercepting cross-domain Ajax calls with service-turtle
A stand alone proxy server like turtle-run
has a downside - it cannot intercept Ajax calls from the page
to other service endpoints. For example, the demo page github-user
makes a request directly from the page to
the Github API
1 | $http.get('https://api.github.com/users/' + $scope.username) |
Can we test how our page handles 500 error from the Github API? We can by modifying the application's code a little and introducing service-turtle. It is a proxy that sits behind the page and can intercept any request. The proxy is implemented using an experimental API called ServiceWorker, and requires running the Chrome browser (at least version 39) with an advanced flag enabled
Open chrome://flags/ in the Chrome browser
Enable "Enable experimental Web Platform features."
Restart Chrome
You can grab the code and run the application with the included service-turtle
by checking out
step-3
and running http-server locally using http-server -p 3003
.
When running the local server, the page loads turtle.js
, which
in turn initializes the service worker using script service-turtle.js
. Every Ajax request from the page
then generates fetch
event inside the ServiceWorker.
The worker can let the fetch
event pass, letting the request through.
The worker can also respond to the event with fake data using event.respondWith
method.
1 | // self - reference to the ServiceWorker itself |
We can confirm that this is working by registering a mock response using turtle
object the script
registers globally
1 | turtle.get('/users/', { code: 200, body: 'hi there' }); |
The object turtle
registered from the bower_components/service-turtle/turtle.js
script
sends the arguments to the service worker by posting a message.
The execution example (first the normal request, then the mocked one) is shown below.
If we want to delay the mock response by N milliseconds, we can using Promise API. We just need to respond with a Promise object
1 | self.addEventListener('fetch', function (event) { |
Testing and handling server error responses
In the next example, we can mock /users
request to return server error 500.
1 | turtle.get('/users/', { code: 500 }); |
Notice that the error is only printed to the console log, no user message appears and the result of the previous fetch is still displayed. Thus the application will NOT handle Github server errors in a user-friendly manner.
It is simple to fix: just put the error message into the result to be displayed
1 | $http.get('https://api.github.com/users/' + $scope.username) |
Now the user will know something bad has happened and will not wait or be confused by previous data.
I plan to extend service-turtle to cover POST, DELETE, PUT and PATCH methods to allow
mocking any request. Even with just GET method support, the service-turtle
seems a very powerful tool
at the cost of including a very small script.
The inclusion could be limited to the development and the staging environments for security.
The service workers are already limited to be loaded from safe urls: localhost
or https
.
This prevents me from demoing this feature from the Github pages, since it does not support SSL.
Another important thing to remember - I do not know what the actual Github server response is for 500 errors. Instead of a plain error string like I used in the mock, it could be an object:
1 | { |
It is important to know the response schema you might want to mock, otherwise you are in for a surprise.
Intercepting request on the fly using ng-wedge
What if we cannot run a proxy between the client and the server (like turtle-run works) and do not want to include a separate ServiceWorker (like service-turtle does)? What if we do not even want to reload our web application, but just want to mock a response? Seems like an impossible task - hooking into the live application and changing its behavior.
Luckily, in case of AngularJS application we can do such substitution on the fly. Other frameworks could potentially
work too, but for now I use mostly AngularJS so I will concentrate on this framework. A typical application (like our
github-user
) has a $scope
object. The $scope
object is initialized inside a controller
function. The controller
also injects $http
service object. This $http
object is used to perform all Ajax requests.
1 | controller('GithubUserController', function ctrl($scope, $http) { |
At runtime the method $scope.load
will be called when the user clicks on a button
1 | <button id="load" ng-click="load()">Load user info</button> |
We can get the reference to the actual $scope
object from the browser's console at any moment.
1 | var $scope = angular.element(document.getElementById('load')).scope(); |
Mocking live $http call
One of the JavaScript's strengths is its dynamic nature. We can easily replace / wrap a method with another method at run time.
1 | var $scope = angular.element(document.getElementById('load')).scope(); |
To mock responses, we do not want to replace the $scope.load
method. Instead we want to somehow replace
the $http object used inside $scope.load
. We can replace it with our own "proxy" $http object that can
pass most calls to the real $http
service, but return mock responses for some requests. In a sense
we want to mock a variable available to the $scope.load
via lexical scope
1 | // application code |
Fortunately this is possible via Faking lexical scope method. In principle we
convert the actual method $scope.load
to its source code using .toString()
, then eval
it back
but prepend the source code with mock $http
variable. Any variable eval
does not find in the source
are plucked from the lexical scope of the place where the eval
is called.
1 | var $scope = angular.element(document.getElementById('load')).scope(); |
The code is modified in page, without any extras needed
Driving an wedge into live production app
Let us go back to our demo at glebbahmutov.com/github-user.
Open the browser console and copy / paste the entire file ng-wedge.js.
ng-wedge
operates on a specific $scope
instance's method. First we make a mock object,
then define a fake response for specific url. Let us return an error message after a delay of 2000ms
1 | var mock = wedge('#load', 'load'); |
Here is how it looks in action
Notice that there is no actual HTTP request in the network tab - the request is
intercepted before the Ajax request happens.
Any other Ajax request, even to the same Github API endpoint happening through other $scope
objects
would NOT be intercepted by ng-wedge
. It only replaces a particular method on a specific $scope
instance.
Summary
I created 3 different proxy tools for conveniently mocking server responses.
- turtle-run - a stand alone proxy between the user and the server. Does not require any modifications to
the server code to run. The user needs to access the proxy URL instead of the actual server url. The proxy
cannot work with
https
servers or proxy cross-domain requests. - service-turtle - a good choice for local development. Requires using the latest version of Chrome browser and turning on an experimental feature support. It is powerful and simple to control on demand, the code is still under development.
- ng-wedge - a good choice for a developer when investigating the production website's behavior.
Does not require adding extra libraries or even reloading the page.
Currently works with AngularJS web applications only, but could be adapted to other frameworks.
Requires a "simple" controller code that can be reused in
eval
call.