Test your service from the controller layer.
Test your service from the controller layer of your application (i.e. your HTTP interface) all the way through to your proxy layer (i.e. the layer that speaks to external dependencies). The HttpRequestScenario allows you to pass in a JSON object representing a NodeJs HTTP Request object. It will mock out the NodeJs HTTP Response object, allowing you to define the expected statusCode, response, etc.
This scenario opens up a type of scenario unit test that some people feel uncomfortable with. That being said, if you can embrace it, it opens up fast, reproducible, and rapid unit testing scenarios that you never knew were possible.
Using this scenario and the philosophies of Behavior Driven Development, you can build out coverage of literally, any use case in your service.
"use strict";
// npm install maddox
const Maddox = require("maddox");
const Scenario = Maddox.scenarios.HttpReqScenario;
describe("When a user saves data", () => {
let context;
beforeEach(() => {
context = {};
context.setupEntryPoint = () => {
context.entryPointObject = SaveDataController; // The controller of our endpoint.
context.entryPointFunction = "save";
};
context.setupInputParams = () => {
context.inputParams = {
dataToSave: {foo: "foo"}
};
};
context.setupDbProxy = () => {
// When reusing parameters it is best practice to copy them to ensure the referenced data changes are not causing...
// unexpected errors in your tests. Maddox will do this for you, but it is good to make it obvious in your test.
context.dbProxyParams = JSON.parse(JSON.stringify(context.inputParams));
context.dbProxyResponse = {code: 200};
};
context.setupExpected = () => {
context.expectedResponse = {success: true};
context.expectedStatusCode = 200;
};
});
it("it should send data to the database.", function (done) {
context.setupEntryPoint();
context.setupInputParams();
context.setupDbProxy();
context.setupExpected();
new Scenario(this)
.mockThisFunction("DbProxy", "save", DbProxy)
.withEntryPoint(context.entryPointObject, context.entryPointFunction)
.withHttpRequest([context.inputParams])
.resShouldBeCalledWith("send", context.expectedResponse)
.resShouldBeCalledWith("status", context.expectedStatusCode)
.resDoesReturnSelf("status")
.shouldBeCalledWith("DbProxy", "save", [context.dbProxyParams])
.doesReturnWithPromise("DbProxy", "save", context.dbProxyResponse)
.test(done);
});
it("it should process the error from the database.", function (done) {
context.setupDbProxy = () => {
const dbError = new Error("Failed to save data.");
dbError.code = 12345;
// When reusing parameters it is best practice to copy them to ensure the referenced data changes are not causing...
// unexpected errors in your tests. Maddox will do this for you, but it is good to make it obvious in your test.
context.dbProxyParams = JSON.parse(JSON.stringify(context.inputParams));
context.dbProxyResponse = dbError;
};
// Let's pretend that our code returns the error code from the db in our response when the database fails.
context.setupExpected = () => {
context.expectedResponse = {success: true, code: 12345};
context.expectedStatusCode = 500;
};
context.setupEntryPoint();
context.setupInputParams();
context.setupDbProxy();
context.setupExpected();
new Scenario(this)
.mockThisFunction("DbProxy", "save", DbProxy)
.withEntryPoint(context.entryPointObject, context.entryPointFunction)
.withHttpRequest([context.inputParams])
.resShouldBeCalledWith("send", context.expectedResponse)
.resShouldBeCalledWith("status", context.expectedStatusCode)
.resDoesReturnSelf("status")
// Notice that we have changed this to "doesErrorWithPromise" instead of "doesReturnWithPromise"...
// since we are testing a scenario for the DbProxy to error.
.shouldBeCalledWith("DbProxy", "save", [context.dbProxyParams])
.doesErrorWithPromise("DbProxy", "save", context.dbProxyResponse)
.test(done);
});
});