Working with mocks

Let's finish by working with a more complex example—part of the REST code that worked with regions, which requires a database and uses promises, among several complications. Let's take the DELETE method handler as an example:

const deleteRegion = async (
res: any,
dbConn: any,
country: string,
region: string
) => {
try {
const sqlCities = `
SELECT 1 FROM cities
WHERE countryCode="${country}"
AND regionCode="${region}"
LIMIT 1
`;

const cities = await dbConn.query(sqlCities);

if (cities.length > 0) {
res.status(405).send("Cannot delete a region with cities");
} else {
const deleteRegion = `
DELETE FROM regions
WHERE countryCode="${country}"
AND regionCode="${region}"
`;

const result = await dbConn.query(deleteRegion);

if (result.info.affectedRows > 0) {
res.status(204).send();
} else {
res.status(404).send("Region not found");
}
}
} catch (e) {
res.status(500).send("Server error");
}
};

We did something right by passing the database connection (dbConn) as a parameter to the function. This means that we can mock it—meaning, provide an alternative version that will behave as we may want it, but without actually using any database. Similarly, processing our request will need to simulate a response object (res) whose status code we'll want to check; we could code it by hand, but using the node-mocks-http package is simpler, so just install it with npm install node-mocks-http --save. Check out its documentation at https://github.com/howardabrams/node-mocks-http, for more information—it can do much more!

We know that the DELETE method should (1) confirm that the region to be deleted must have no cities, and (2) if true, then actually delete the region. How can we test if the first check works? Let's provide deleteRegion() with a mock that will say that the region we want to delete actually has some cities:

// Source file: src/restful_regions.test.js

/* @flow */
"use strict";

const { deleteRegion } = require("./restful_regions");
const mockRes = require("node-mocks-http");

describe("deleteRegion", () => {
let mDb;
let mRes;
beforeEach(() => {
mDb = { query: jest.fn() };
mRes = new mockRes.createResponse();
});

it("should not delete a region with cities", async () => {
mDb.query.mockReturnValueOnce(Promise.resolve([1]));
await deleteRegion(mRes, mDb, "FK", "22");
expect(mRes.statusCode).toBe(405);
});

// continues...

We could program a complete mock database that would analyze the incoming query and then provide some expected answer, but in this case, a little knowledge about how the code checks for cities is good. We can create a mock database object with a query attribute (mDb.query) and set it so that when mDb.query() is called for the first time, it will return a promise resolved to an array with a single 1—for that's what the actual SQL statement would have produced when checking a region that actually includes some cities. We'll also create a mock response object (mRes) that will get the routine's answer.

What's left to do? You just have to call the deleteRegion() function with all the parameters, await its results, and verify that the response status code is 405, as expected; then, you're done! 

The other tests are similar, but we have to simulate two SQL accesses, not one: 

// ...continued

it("should delete a region without cities", async () => {
mDb.query
.mockReturnValueOnce(Promise.resolve([]))
.mockReturnValueOnce(
Promise.resolve({
info: { affectedRows: 1 }
})
);
await deleteRegion(mRes, mDb, "ST", "12");
expect(mRes.statusCode).toBe(204);
});

it("should produce a 404 for non-existing region", async () => {
mDb.query
.mockReturnValueOnce(Promise.resolve([]))
.mockReturnValueOnce(
Promise.resolve({
info: { affectedRows: 0 }
})
);
await deleteRegion(mRes, mDb, "IP", "24");
expect(mRes.statusCode).toBe(404);
});
});

The interesting thing is that we can set up a mock function to produce different answers each time it is called, according to what we need. Thus, in order to test whether deleteRegion() would correctly delete a region without cities, our mock DB object must do the following:

  • First, return an empty array, showing that the region to be deleted has no cities
  • Second, return an object with affectedRows:1, showing that the (supposed) DELETE SQL command was successful

After setting things up in this way, the rest of the code is like our first case; await the function and check the status code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.137.75