Appearance
Mocking HTTP services
Tested code might rely on remote HTTP services which need to be mocked for unit testing. SDT provides a method for conveniently generating HTTP services responding to incoming requests based on a simple configuration.
Example
Let's start with an example displaying the bare minimum necessary to set up an HTTP service for use in tested code.
javascript
import { createHttpServer } from "@hitchy/server-dev-tools";
describe( "Testing my code", () => {
let server;
beforeAll( async() => {
server = await createHttpServer( {
"/foo": { text: "bar" },
} );
} );
afterAll( () => {
server.close();
} );
// TODO add your testing code including starting/stopping Hitchy as necessary
} );
Is Hitchy required?
The HTTP service itself does not require some Hitchy runtime to be started and stopped. Because of that, the example above is explicitly omitting any code setting up Hitchy for testing code relying on it.
Basics
The asynchronous method createHttpServer()
is invoked to create an HTTP service ready for responding to requests based on provided configuration. It may be invoked multiple times to have multiple services to work with in parallel. Either created service is available locally, only, on a dedicated port.
API
The method takes a configuration as sole argument.
The resulting HTTP service is based on Node's native HTTP server. It comes with a few extensions, though:
baseUrl
This property provides the base URL of the created HTTP service. It can be used e.g. for compiling request URLs or for passing it in configurations of tested code.
setOverlay
This method takes a configuration as argument which is shallowly merged with the one provided on creating the HTTP service. Requests configured in this configuration is preferred over the one provided on creating the service. It is designed to adjust a running service's behavior temporarily to meet a test's particular expectations.
Mind the process!
Usually, testing Hitchy-based code does not spawn new processes. HTTP services get created in same process as your test runner, too. That's why using overlays to temporarily adjust a running HTTP service's configuration is working.
Keep this in mind in case your test or the tested code is spawning processes explicitly.
javascript
it( "behaves properly", async() => {
server.setOverlay( {
"/foo": { text: "baz" },
} );
// requests sent to `server` respond with updated configuration now
} );
On setting another overlay, the previous overlay gets replaced:
javascript
it( "behaves properly", async() => {
server.setOverlay( customConfig );
// requests sent to `server` respond with updated configuration now
server.setOverlay( customAltConfig );
// requests sent to `server` respond with latest update to the configuration
} );
When invoked without any argument, the original configuration is restored:
javascript
it( "behaves properly", async() => {
server.setOverlay( {
"/foo": { text: "baz" },
} );
// requests sent to `server` respond with updated configuration now
server.setOverlay();
// requests sent to `server` respond with original configuration again
} );
An overlay may provide a falsy response configuration for an endpoint to respond to some request matching that endpoint in the same way as if there was no matching endpoint.
Configuration
The HTTP service configuration is an object mapping endpoints into response configurations used whenever a matching request is received.
Endpoint
An endpoint is a pathname optionally prefixed with an HTTP request method.
text
POST /api/foo/bar
A request is considered matching when both method and pathname are matching. The HTTP request method is optional in which case any method is matching.
text
/api/foo/bar
If both cases match in a configuration, the one with the method prefix is preferred over the one lacking it.
Response configuration
A response configuration is either
- an object with optional properties describing how to respond to a matching request or
- a function invoked on every matching request to deliver that set of properties.
On providing a function, it is invoked with both the request descriptor and the response manager as arguments. Because of that, some callback can e.g. adjust the response in its own ways. By returning false instead of a response configuration, the response is considered as completely sent to the client already.
body
This property provides a string or Buffer sent as payload in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
css
This property provides a string sent as CSS in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
file
This property names a local file to be read. Its content is delivered in response to a matching request. The file's extension is used to pick a content type reported to the requesting client, falling back to application/octet-stream
.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
html
This property provides a string sent as HTML in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
javascript
This property provides a string sent as Javascript in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
json
This property provides some data sent as JSON in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
status
This property describes the HTTP status code to be used in response to a matching request. It defaults to 200.
text
This property provides a string sent as plain text in response to a matching request.
Convenience feature
This property is one out of multiple options, and it must not be mixed with other such properties.
headers
This is an object mapping response header names into either header's value.
Example
javascript
const server = await createHttpServer( {
"/api/foo/bar": {
text: "baz"
},
"POST /api/foo/bar": {
json: { baz: "boo" },
headers: {
"x-assigned-id": 12345678
}
},
"DELETE /api/foo/bar": () => ( {} ),
"PUT /api/foo/bar": ( req, res ) => {
res.end( "foo" );
return false;
},
} );