30. Testing database state

This unit test would be more robust if we not only checked the HTTP response, but we also checked the database to ensure state has been modified as we're intending.

Clearing the database

The database contents will obviously persist across tests, which makes it unpredictable. For this reason, we should make sure the database is cleared before every test runs to ensure that pre-existing data doesn't interfere with our tests.

To do this, we'll add a Mocha lifecycle hook at the top of our test file called beforeEach. As the name suggests, it runs the provided function before each test case.

tests/unit/server/item.test.js

beforeEach(() => {

});

We'll use this hook to clear our existing items.

Removing items

You'll recall from the last module that our Mongoose model has a method save. Well, Mongoose models actually have many different methods which allow you to perform CRUD operations on the model data.

So let's begin by requiring our Item model at the top of the file so we have a way of working with it.

tests/item.test.js

const Item = require("../../../server/models/Item");

Now, we're going to call Item.deleteMany() which, if you don't provide any criteria, will simply delete every item and therefore clear our database.

tests/item.test.js

beforeEach(() => {
  Item.deleteMany();
});

Like most Mongoose methods, deleteMany is asynchronous. So let's give this hook an async method, which means we can just return Item.deleteMany and it will do what we want.

beforeEach(async () => Item.deleteMany());

Asserting an item

Now that we've got a predictable database state, we can make some assertions about it.

You'll recall that our POST /items endpoint will write a new item to the database. So let's attempt to retrieve this item in our test so we can confirm it worked.

Do do this, we'll call Item.find. The find method will check the database for anything matching the provided criteria.

If we don't provide critera, it will simply return all the items in the database in an array, which we'll assign to a variable items.

We only added one item in this test, so let's assert that by going expect(items.length).toBe(1);

We're also going to expect that item's title to be the title we defined above, allowing us to check not only that our item exists, but that it was written correctly.

We can do this by going expect(items[0].title).toBe(body.title);.

const body = { title: "Test title" };
    const res = await request(app)
      .post("/items")
      .send(body)
      .expect(200);
    expect(res.body.item.title).toBe(body.title);
    const items = await Item.find();
    expect(items.length).toBe(1);
    expect(items[0].title).toBe(body.title);

If you run this now, you'll see that it passes.

Test database

Before we finish this video, let's make it so we actually have two databases; one for development and one for testing.

The reason is that any time we run tests we'll be clearing our database, which is going to be annoying since we may want to keep some data in the database for smoke tests during development.

Luckily, this is trivial to implement. In our mongoose.connect method, instead of just a string to the first argument, let's make it a template literal and replace the database name with a variable dbName.

Above this, we'll then create the variable dbName using a ternary operator and check the NODE_ENV environment variable equals "test".

If it does, it means the server is being loaded by Supertest as part of a Mocha test, in which case we'll use the database "printbay_test", otherwise, just "printbay".

server/index.js

const dbName = process.env.NODE_ENV === "test" ? "printbay_test" : "printbay";
mongoose.connect(
  `mongodb://localhost:27017/${dbName}`,
  { useNewUrlParser: true, useCreateIndex: true }
);

Now run the test suite again and let's make sure everything is working.

Finally, we'll go to Compass, and confirm that we now have two databases in use.

Discussion

0 comments