Covering all code

For now, let's focus on adding coverage to our code. It's important to have it covered as much as possible when it's still just a small service. If we start adding tests and coverage when it's already big, you'll be frustrated, and it will be hard to find the motivation to cover it all.

This way, you'll find it rewarding to cover it in the beginning and keep the coverage percentage as high as possible along with code evolution.

Let's get back to our image upload test, and add another test:

it("should deny duplicated images", (done) => {
chai
.request(tools.service)
.post("/uploads/test_image_upload.png")
.set("Content-Type", "image/png")
.send(tools.sample)
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.status("ok");

chai
.request(tools.service)
.post("/uploads/test_image_upload.png")
.set("Content-Type", "image/png")
.send(tools.sample)
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.status("error");
chai.expect(res.body).to.have.property("code",
"ER_DUP_ENTRY");

return done();
});
});
});

This will upload the same image twice in a row and we should receive an error from the database saying there's a duplicate. Let's run the tests again:

Now, let's open the initial page  of the coverage report:

Notice that our file is no longer in a red background. This means the statement coverage has reached 50%. Let's click on our file and see how our image upload method is covered:

It's complete! We can now move on. Just a reminder before we move to another method: having full coverage does not mean there are no bugs. That's something you need to understand. You might have a use case that you're not expecting, and so, you have no code for it, so there's no obvious coverage.

For example, the bodyparser module will not limit the type of content. If we upload a text file with an image name on it, our code will accept it and store it in the database without noticing. Think of this use case as your homework, and try to create a test to cover that use case and then fix the code.

Let's move to the next method we see after our upload method: the image check on line 67. Let's create a new integration test file called image-check.js, and add a simple test:

const chai = require("chai");
const http = require("chai-http");
const tools = require("../tools");

chai.use(http);

describe("Checking image", () => {
beforeEach((done) => {
chai
.request(tools.service)
.delete("/uploads/test_image_check.png")
.end(() => {
return done();
});
});

it("should return 404 if it doesn't exist", (done) => {
chai
.request(tools.service)
.head("/uploads/test_image_check.png")
.end((err, res) => {
chai.expect(res).to.have.status(404);

return done();
});
});

it("should return 200 if it exists", (done) => {
chai
.request(tools.service)
.post("/uploads/test_image_check.png")
.set("Content-Type", "image/png")
.send(tools.sample)
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.status("ok");

chai
.request(tools.service)
.head("/uploads/test_image_check.png")
.end((err, res) => {
chai.expect(res).to.have.status(200);

return done();
});
});
});
});

Let's run the test suite:

We can see our console report is getting bigger. As we're creating new integration test files and having a description for each one, mocha writes a nice tree view showing how the tests run. On the bottom, we can see the coverage report:

Looking at the check method, we see it's now fully covered. This one was very simple.

We're still in the middle of statement coverage as our top method; the image manipulation one is almost half of our code. This means that when we start covering it, the coverage will significantly rise.

Let's create an integration test for it:

const chai = require("chai");
const http = require("chai-http");
const tools = require("../tools");

chai.use(http);

describe("Downloading image", () => {
beforeEach((done) => {
chai
.request(tools.service)
.delete("/uploads/test_image_download.png")
.end(() => {
chai
.request(tools.service)
.post("/uploads/test_image_download.png")
.set("Content-Type", "image/png")
.send(tools.sample)
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.status("ok");

return done();
});
});
});

it("should return the original image size if no parameters given",
(done) => {
chai
.request(tools.service)
.get("/uploads/test_image_download.png")
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.length(tools.sample.length);

return done();
});
});
});

Before each test, we're deleting the image (if it exists) and then uploading a fresh sample one. Then, for each test, we'll download it and test the output according to what we asked for.

Let's try and run it:

Well, that was unexpected. The test fails because our length check does not match. This is actually a good example of something we just notice when we start to execute testing.

What happens is that, when we request an image, we use the sharp module to make any manipulation on the image, according to query parameters. In this case, we're not asking for any manipulation, but when we output the image (through sharp), it actually returns the same image in size, but perhaps with a little bit less quality, or maybe it just knows how to better encode our image and remove data from the file that is not needed.

We don't know exactly, but let's assume we want the original image, untouched. We need to change our download method. Let's assume that if no query parameters are defined at all, we just return the original image. Let's add a condition to the top of our method:

if (Object.keys(req.query).length === 0) {
db.query("UPDATE images " +
"SET date_used = UTC_TIMESTAMP " +
"WHERE id = ?",
[ req.image.id ]);

res.setHeader("Content-Type", "image/" +
path.extname(req.image.name).substr(1));

return res.end(req.image.data);
}

If we run  it now, we should have no failures:

Our statement coverage did not rise much because we actually created a condition on top of the method and returned immediately, so our previous method is still untested:

Looking at line 78, you should see a new mark, an E that means that the condition in that line never executed the else statement, which is the rest of our code. Let's add a test to this integration and resize our image.

We will need sharp to help us check whether the results are correct. Let's include it on the top of our file:

const sharp = require("sharp");

Then, add a resize test:

it("should be able to resize the image as we request", (done) => {
chai
.request(tools.service)
.get("/uploads/test_image_download.png?width=200&height=100")
.end((err, res) => {
chai.expect(res).to.have.status(200);

let image = sharp(res.body);

image
.metadata()
.then((metadata) => {
chai.expect(metadata).to.have.property("width", 200);
chai.expect(metadata).to.have.property("height", 100);

return done();
});
});
});

Let's run our test suite:

It now looks very good. From the console report, we can see some green. Let's look at the front page of the coverage report:

We see green here as well. Having more than 80% coverage is good, but we can still go further. Let's see the file:

It's more or less covered. We still need to cover all the effects. We can actually run them all at once. The first two conditions also have an E marker, but that should disappear after adding a test without resizing. Let's add it:

it("should be able to add image effects as we request", (done) => {
chai
.request(tools.service)
.get("/uploads/test_image_download.png?
flip=y&flop=y&greyscale=y&blur=10&sharpen=10")
.end((err, res) => {
chai.expect(res).to.have.status(200);

return done();
});
});

Looking at our report now, we see the coverage is almost complete:

To cover those yellow nulls there, we need to resize the image with only width or height. We can add two tests for those:

it("should be able to resize the image width as we request", (done) => {
chai
.request(tools.service)
.get("/uploads/test_image_download.png?width=200")
.end((err, res) => {
chai.expect(res).to.have.status(200);

let image = sharp(res.body);

image
.metadata()
.then((metadata) => {
chai.expect(metadata).to.have.property("width", 200);

return done();
});
});
});

Add a similar one for the height, and run the test suite. You should not see the statement coverage go up, only the branch coverage:

The only method missing is the statistics method. This one is simple. We could eventually run a more specific test, by asking statistics, making a change such as an upload, and asking for statistics again to compare. I'll leave that to you. Let's just add a simple request test:

const chai = require("chai");
const http = require("chai-http");
const tools = require("../tools");

chai.use(http);

describe("Statistics", () => {
it("should return an object with total, size, last_used and
uptime", (done) => {
chai
.request(tools.service)
.get("/stats")
.end((err, res) => {
chai.expect(res).to.have.status(200);
chai.expect(res.body).to.have.property("total");
chai.expect(res.body).to.have.property("size");
chai.expect(res.body).to.have.property("last_used");
chai.expect(res.body).to.have.property("uptime");

return done();
});
});
});

Now, running our test suite should give all green:

We see there are only two lines uncovered: 29.121. The first one is our timer and the second one is on the statistics method. Let's refresh our HTML report:

This is rewarding; we have almost 100% coverage. There's only one function not covered, which is our timer. And, there are only tree statements, which also represent the three branches, that aren't covered, but those aren't actually that important.

What is important is to keep this high coverage mark during the course of our development.

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

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