angular-http-raml
v0.0.6
Published
testing library for creating angular 4 Http instances based on a RAML definition
Downloads
2
Readme
Table of Contents generated with DocToc
angular-http-raml
This library provides a RAML RAML-based MockBackend
generator for testing Angular 4 applications.
Simply put, you can define your REST API using RAML, and use this definition to create an angular Http object which speaks the same language as the API you defined.
Installation
Install the library with npm install --save-dev angular-http-raml
Then add the following entry to your karma configuration:
files: [
...
{pattern: "**/*.raml", included: false}
...
],
Stubbing
Prerequisities:
To understand the below walk-through, you need to have a basic understanding of the followings:
- Angular 2+
- RAML
- Jasmine
- Test doubles (mocks and stubs)
Quickstart
This library generates stub HTTP backends
Let's assume your application interacts with the API defined by the following RAML file:
person-api.raml:
#%RAML 1.0
title: Endpoints for testing response selection based on status codes
version: v0
baseUri: http://api.example.com
mediaType: application/json
/person/{personId}/emails:
get:
responses:
200:
queryParameters:
activeOnly: boolean
body:
example:
- email: "[email protected]"
active: true
- email: "[email protected]"
active: false
404:
body:
example:
message: "not found"
post:
responses:
201:
Let's also assume that you want to test the following Angular service:
my-service.ts:
export class MyService {
constructor(private http: Http) {}
public fetchPersonEmails(personId: number): Observable<string[]> {
return http.get("http://api.example.com/person/" + personId + "/emails")
.map(response => response.json());
}
}
my-service.spec.ts :
describe("MyService", () => {
it("queries the backend", () => {
const mockBackend = RAMLBackendConfig.initWithFile("./person-api.raml")
.stubAll()
.createBackend();
const http = new Http(mockBackend, new RequestOptions());
const subject = new MyService(http);
subject.fetchPersonEmails(123).subscribe(emails => {
expect(emails).toEqual([
{email: "[email protected]", active: true},
{email: "[email protected]", active: false},
]);
});
mockBackend.verifyNoPendingRequests();
});
});
Here is what happens when you run this test:
- the
RAMLBackendConfig.initWithFile("./person-api.raml")
call looks up the REST endpoints with their parameters. These are the possible calls to be used on the mock - the
stubAll()
call tells theRAMLBackendConfig
instance to stub all requests defined in the RAML file. The responses will be the entities defined in theexample
node of the RAML definition (if present). - then we instantiate a
Http
object which will use our generatedMockBackend
. - when your
MyService
method calls thehttp.get(...)
method then the generated stub will know that theGET http://api.example.com/person/123/emails
call matches the stubbedGET /person/{personId}/emails
endpoint, so it will pick up theexample
array and return it as the response body. Unless you specify it otherwise it will look for a 2xx response in the listed responses. - the
MyService
instance receives the response (just like if it would be a real HTTP backend service), and publishes the result to the subscriber attached in the test. This subscriber will perform the assertion (if the response ofMyService
is correct).
This was the quick-start of using the library. To sum up, if you develop a RAML file which includes example responses, then you generate a HTTP stub backend from it in a few lines.
Customizing stubbing
Changing the response by response code
RAML lets you describe responses with multiple response codes. By default, angular-http-raml will choose the response with the lowest 2xx status code, but you can simply
override this behavior. Example (using the above listed person-api.raml
):
// ...
const mockBackend = RAMLBackendConfig.initWithFile("./person-api.raml")
.stubAll()
.whenGET("/person/123/emails").thenRespondWith(404)
.createBackend();
// ...
This configuration will override the default stubbing and tell the RAMLBackend to send the 404 response instead of the default 200. The response body will be {"message":"not found"}
as it is defined in the RAML file.
Using an other example response body
RAML lets you define multiple example response bodies for a response. angular-http-raml lets you easily switch between them. Let's consider the following RAML definition of an endpoint:
/person/{personId}/emails:
get:
responses:
200:
body:
examples:
hasEmails:
- email: "[email protected]"
active: true
- email: "[email protected]"
active: false
emptyList: []
In this RAML definition there are 2 example responses (both with 200 response code). They are called hasEmails
and emptyList
. By default the RAMLBackend
instance would return the
first example response it finds (hasEmails
), override it by passing emptyList
as the 2nd parameter of thenRespondWith()
:
const mockBackend = RAMLBackendConfig.initWithFile("./person-api.raml")
.stubAll()
.whenGET("/person/123/emails").thenRespondWith(404, "emptyList")
.createBackend();
Passing an entire request
If the above two methods of overriding default responses - eg. because the response body you want to test with is different from the examples available in RAML - then you can explicitly
pecify the entire request to be sent back from the server by calling thenRespond()
instead of thenRespondWith()
:
const mockBackend = RAMLBackendConfig.initWithFile("./person-api.raml")
.whenGET("/person/123/emails").thenRespond(new Response(new ResponseOptions({
status: 200,
body: JSON.stringify([{email:null, active: false}]);
})))
.createBackend();
Safety nets while stubbing
One thing that can go wrong while stubbing - or creating any kind of test double - is mimicing false behavior of the stubbed system. As a simple example, if you mistype a request path in your test, then you implement a service that conforms to the test, then your test will pass, but your service still won't be compatible with your backend.
To minimize the risk of such problems, angular-http-raml
validates your stubbed requests, so it checks if you properly mimic the behavior of the remote service. In the below example the stub
request contains a query parameter called onlyActive
, but since this query parameter is not included in the RAML definition, an InvalidStubbingError
will be thrown:
const mockBackend = RAMLBackendConfig.initWithFile("./person-api.raml")
// thow will throw an InvalidStubbingError immediately,
// since "onlyActive" is not a valid query param
.whenGET("/person/123/emails?onlyActive=true").thenRespond(200)
.createBackend();
Similarly, eg. if you try to stub a request which doesn't match any path patterns then an error is thrown.
Does it mean that you can't have any compatibility issues with server-client communication? Of course, not. The library conforms to your RAML documentation and not to your real server. To take a full advantage of RAML, it is recommended to involve the same file in testing your backend, so the contract is verified from both ends. This library can help you with only the half of the job.
Mocking
TODO