Angular Testing Snippets: Services over HTTP
Faking HTTP responses for domain model services
In single-page applications, business logic is often implemented over HTTP, translating application behaviour to URLs, request methods, status codes, and HTTP entities. This story presents a testing pattern for domain model services that implement RPC-like contracts between clients and servers. All is done by writing plain-old Angular tests: test specs in Jasmine, executed by Karma in a browser, test coverage from Istanbul, right from either WebStorm or VSCode.
Step #1: Implementing a super-duper feature
As an example, let’s take a simple login. It could be any other feature implementation of your choice:
Step #2: Test Set Up
Building upon the same idea discussed here, we are going to set up a mock backend for faking HTTP traffic. It serves two purposes: the MockBackend
will be scripted to simulate a real backend service in our test specifications. Also, MockBackend
will be injected as a drop-in replacement for XHRBackend
, thus Http
and dependees like FeatureService
will issue requests that are served by the mock backend. Here’s the glueing code:
Step #3: Testing for Errors
Why are we writing all this boilerplate code? Well, we’d like battle-tested implementations that withstand even in faulty situations. So let’s go for testing a login failure first.
We’re pretending 401 Unauthorized
is the response code for invalid credentials – the fake HTTP response is scripted in (3) in the following code listing. In (4), we assert that implementation of FeatureService
responds to that by emitting a false
value. Also, in (2a) and (2b), we’re asserting that the implementation issued a request that the server expected. The numbers in the code listing indicate the chronological order, helping us humans to trace the async flows.
If you like, you could split up the test into two separate specs. One that runs the expectations on the request
, thus ensuring FeatureService
satisfies the server-side API. A second one that runs the expectation on status: boolean
, thus ensuring correct behaviour for users of FeatureService
— typically, this would be components or other parts of the application.
This way, we’re running expectations at both ends: correct implementation against the API we’re depending on and correct behaviour towards users who depend on us.
Step #4: Testing for Success Cases
Rounding up our test specification, we should also test for the rare cases of success. The code listing should look familiar to you:
Summary
Let’s recall: what is the specific behaviour of the login feature?
First, the login method issues a POST /auth/login
with form-encoded params for user
and password
. Second, response status codes from 200 to 299 are mapped to an Observable
emitting true
(obviously, that’s indicating login success). Any other response code and errors emit false
to the caller (consequently, reporting login failures).
We’ve written two straight-forward test specifications ensuring expected behaviour of the client-side implementation in FeatureService
for both success and error cases.
This kind of testing pattern can easily be scaled out to more complex services that run business logic over HTTP. It will help you to build battle-tested and bullet-proof implementations in your Angular apps!