Testing your IndexedDB code with Jest
Tags: javascript, indexeddb, tutorial, testing
Published: 2019-05-14 23:17:38

---
title: Testing your IndexedDB code with Jest
published: true
description: 
tags: #javascript #indexeddb #tutorial #testing
cover_image: https://thepracticaldev.s3.amazonaws.com/i/yamqqzfs6d8jjo1ifffz.jpg
---

In [my last IndexedDB tutorial](https://medium.com/@AndyHaskell2013/build-a-basic-web-app-with-indexeddb-8ab4f83f8bda), we looked at the basics of IndexedDB’s API for building a small web app. However, although IndexedDB is a powerful API for giving your web apps a client-side database, it definitely took me a while to figure out how to give an IndexedDB app automated test coverage so we know it works how we expect it to.

If everything is asynchronous in IndexedDB's API, how would we write some tests for our IndexedDB database layer? And how do we get our tests to use IndexedDB when it's a browser API? In this tutorial, we're going take two asynchronous IndexedDB functions and see how to test them out with Jest.js.

This tutorial does assume you know the basics of IndexedDB and of automated testing in JavaScript.

## Checking out our code:

Inside our `db.js` file, you can see the code we're running ([commit 1](https://github.com/andyhaskell/indexeddb-tutorial/commit/445df5c44bc8c0c8434d7842d959d037f5be7dc8)), which is a sticky note database based on the code in the last tutorial. There are functions that talk directly to IndexedDB:

* `setupDB` is used for our database. We store the IndexedDB database object in a global variable called `db`, which is initialized once `setupDB` completes.
* `addStickyNote` takes in a string and adds sticky note of that message to the database.
* `getNotes` retrieves all of the sticky notes in the database, either in forward or reverse order.

Since these functions are how we talk to IndexedDB, one of the things we'll want to test out in our database tests is that if we put some sticky notes into the database with `addStickyNote`, we can get all of them back in the correct order with `getNotes`. So the test code we want might look something like this:

```javascript
setupDB();
addStickyNote("SLOTHS");
addStickyNote("RULE");
let notes = getNotes();
// Run assertions that we got back the sticky notes we wanted
```

However, remember that IndexedDB is an asynchronous API, so when we run those calls to `addStickyNote`, the JavaScript runtime starts the database transactions, but it doesn't wait for them to finish. Because of that, the two calls to `addStickyNote` aren't necessarily done when we're running `getNotes`. Not only that, but `setupDB` isn't necessarily done when we start `addStickyNote`, so it's possible that `addStickyNote` could be run while the `db` variable is still undefined!

So in order to run our IndexedDB functions so that each one runs in order, the code in this tutorial is designed to have each IndexedDB function take in a **callback** function as one of its parameters.

## Chaining our IndexedDB functions with callbacks

To see callbacks on our IndexedDB functions, let's take a look at the flow of `setupDB`:

```javascript
function setupDB(callback) {
  // If setupDB has already been run and the database was set up, no need to
  // open the database again; just run our callback and return!
  if (db) {
    callback();
    return;
  }

  let dbReq = indexedDB.open('myDatabase', 2);

  // Fires when the version of the database goes up, or the database is created
  // for the first time
  dbReq.onupgradeneeded = function(event) {
    db = event.target.result;
  
    // Create an object store named notes, or retrieve it if it already exists.
    // Object stores in databases are where data are stored.
    let notes;
    if (!db.objectStoreNames.contains('notes')) {
      db.createObjectStore('notes', {autoIncrement: true});
    }
  }
  
  // Fires once the database is opened (and onupgradeneeded completes, if
  // onupgradeneeded was called)
  dbReq.onsuccess = function(event) {
    // Set the db variable to our database so we can use it!
    db = event.target.result;
    callback();
  }
  
  // Fires when we can't open the database
  dbReq.onerror = function(event) {
    alert('error opening database ' + event.target.errorCode);
  }
}
```

Just like in the last tutorial, this code makes a request to open our database. If the database is being created for the first time, then we run the request's `onupgradedneeded` event handler to create our object store. Then, based on whether the request succeeds or fails, we either run the request's `onsuccess` event handler to populate our `db` global variable, or we alert that there was an error opening the database.

Something to draw your attention to, though, is how we use the `callback` parameter. There are two places in the code to run the callback:

```javascript
if (db) {
  callback();
  return;
}
```

* If `db` __isn't__ undefined, then that means `setupDB` has already been called once and we have our database, so we don't need to do anything to set up our database; we can just run the callback that was passed in.

```javascript
dbReq.onsuccess = function(event) {
  // Set the db variable to our database so we can use it!
  db = event.target.result;
  callback();
}
```

* The other place `callback` can be called is in our database request's `onsuccess` event handler, which is called when our database is completely set up.

In both cases, we only call `callback` once our database is set up. What that does for us is that by having each of our IndexedDB functions take in a callback parameter, we know that when the callback runs, that function's work is completed. We can then see this in action in `index.html`, where we use that callback parameter to run one IndexedDB function after another:

```html
<script type="text/javascript">
  setupDB(getAndDisplayNotes);
</script>
```

We run `setupDB`, and then since we know we now have a `db` variable set, we can run `getAndDisplayNotes` as `setupDB`'s callback to display any existing sticky notes in the web app.

So with those callbacks, we have a strategy for our tests to run IndexedDB functions in order, running one database action as the last action's callback. So our test would look like this:

```javascript
setupDB(function() {
  addStickyNote("SLOTHS", function() {
    addStickyNote("RULE", function() {
      getNotes(reverseOrder=false, function(notes) {
        //
        // Now that we have retrieved our sticky notes, in here we test that
        // we actually got back the sticky notes we expected
        //
      });
    });
  });
});
```

The callback pyramid is a bit hard to follow, and in a later tutorial I'll show how we can refactor IndexedDB's callback-based API to be promise-based instead, but for now, we've got a way to guarantee that one IndexedDB action happens after the last one, so with that, we've got a way to test our IndexedDB code, so let's dive into the test!

## Writing the test

The code changes for this section are in [commit 2](https://github.com/andyhaskell/indexeddb-tutorial/commit/ac83e68db2a96192dc3af42483fe48344dc9489f)

The first thing we'll need for our IndexedDB tests is to install a testing framework as one of our project's dependencies. We'll use [Jest](https://jestjs.io) for this tutorial, but you can use really any testing framework that supports testing asynchronous functions; an IndexedDB test in [Mocha](https://mochajs.org) + [Chai](https://www.chaijs.com) for example would have a similar structure overall to one in Jest.

```
yarn add --dev jest
```

Now that we've got our test program, we can make our `db.test.js` file to run our test in, but we'll need one extra line of code in `db.js` so that db.test.js can import its functions.

```javascript
module.exports = {setupDB, addStickyNote, getNotes};
```

NOTE: This line does mean `index.html` can no longer use `db.js` as-is since the browser can't currently recognize `module.exports`. So for this code to still be used in our web page, we will need a code bundler like webpack. We won't go into depth on how to get that set up, but if you are learning webpack and looking for a step by step webpack tutorial, you can check out my tutorial on it [here](https://dev.to/andyhaskell2013/webpack-from-0-to-automated-testing-3n23), and you can check out my code to get this webpack-ready at [commit #5](https://github.com/andyhaskell/indexeddb-tutorial/commit/045dac8f7321728c1cb2cb986a91df581f958b43).

Now here goes. In `db.test.js`, add this code:

```javascript
let {setupDB, addStickyNote, getNotes} = require('./db');

test('we can store and retrieve sticky notes', function(done) {
  setupDB(function() {
    addStickyNote('SLOTHS', function() {
      addStickyNote('RULE!', function() {
        // Now that our sticky notes are both added, we retrieve them from
        // IndexedDB and check that we got them back in the right order.
        getNotes(reverseOrder=false, function(notes) {
          expect(notes).toHaveLength(2);
          expect(notes[0].text).toBe('SLOTHS');
          expect(notes[1].text).toBe('RULE!');
          done();
        });
      });
    });
  });
});
```

At the beginning of the code, we're importing our code for talking to IndexedDB. Then, we run our test:

```javascript
test('we can store and retrieve sticky notes', function(done) {
```

`test` is the Jest function for running our test case and the function we pass into `test` is where we run our code and check that it does what we expect it to do.

As you can see, that anonymous function takes in an argument called `done`, and that's because since we're testing IndexedDB, this is an [**asynchronous** test](https://jestjs.io/docs/en/asynchronous). In a regular Jest test, the anonymous function doesn't have any arguments, so when that function returns or reaches the closing curly brace, the test is over and Jest can move on to the next text. But in asynchronous tests, when we get to the right brace of the anonymous function, we're still waiting for our IndexedDB code to finish, so we instead call `done()` when it's time to tell Jest that this test is over.

```javascript
setupDB(function() {
  addStickyNote('SLOTHS', function() {
    addStickyNote('RULE!', function() {
```

Inside our anonymous function, we run `setupDB`, then in its callback, we know that our database is open, so we can add a sticky note that says "SLOTHS" into IndexedDB with `addStickyNote`, and then add another one after it that says "RULE".

Since each callback is only run after the last IndexedDB action had completed, when we get to `getNotes`, we already know that our two sticky notes are in the database, so we run `getNotes` and in its callback, we check that we got back the sticky notes in the right order.

```javascript
getNotes(reverseOrder=false, function(notes) {
  expect(notes).toHaveLength(2);
  expect(notes[0].text).toBe('SLOTHS');
  expect(notes[1].text).toBe('RULE!');
  done();
});
```

Inside `getNotes`'s callback, we check that we got back two sticky notes, the first one says "SLOTHS", and the second one says "RULE!" Finally, we call the `done()` function in our test's anonymous function so we can tell Jest that the test is over.

Run the test with `npx jest` and...

![Test failed; ReferenceError: indexedDB is not defined](https://thepracticaldev.s3.amazonaws.com/i/1dye3y69cb2do3cyrysf.png)

## Fake-indexeddb to the rescue!

The reason why our test didn't work is because `indexedDB` is undefined in the global namespace; IndexedDB is a browser API, so does exist in the global namespace in a browser's `window` object, but in a Node environment, the `global` object does not have an IndexedDB.

Luckily, there is a JavaScript package that we can use to get a working IndexedDB implementation into our code: [fake-indexeddb](https://github.com/dumbmatter/fake-indexeddb)!

```
yarn add --dev fake-indexeddb
```

Fake-indexeddb is a completely in-memory implementation of the [IndexedDB spec](https://www.w3.org/TR/IndexedDB-2/), and that means we can use it in our tests to use IndexedDB just like we'd use it in a browser. How to we use it, though? Head over to `db.test.js` and add this code ([commit 3](https://github.com/andyhaskell/indexeddb-tutorial/commit/98fcfe9feac293067a6a5ada0443753a7c2c309d)):

```javascript
require("fake-indexeddb/auto");
```

Then run `npx jest` again and...

![Our test in the command line now passes!](https://thepracticaldev.s3.amazonaws.com/i/zg5h8urljtowqija6784.png)

With just one line, IndexedDB is up and running and our test works just as expected! That one import, `fake-indexeddb/auto`, populates Node.js's `global` object with an `indexeddb` variable, as well as types like its `IDBKeyRange` object for free! :fire:

To test against an actual browser's IndexedDB implementation, to the best of my knowledge you'd need an in-browser testing framework, such as with Selenium, but fake-indexeddb implements the same IndexedDB spec, so that still gives us good mileage for unit tests; real-browser testing is at the end-to-end test level.

## Namespacing our tests

Let's add one more test case. `getNotes` has a reverse-order parameter for getting our notes in reverse order, and testing it has the same structure; open the database, add two sticky notes, then run `getNotes`, this time with `reverseOrder` being true.

```javascript
test('reverse order', function(done) {
  setupDB(function() {
    addStickyNote('REVERSE', function() {
      addStickyNote('IN', function() {
        getNotes(reverseOrder=true, function(notes) {
          expect(notes).toHaveLength(2);
          expect(notes[0].text).toBe('IN');
          expect(notes[1].text).toBe('REVERSE');
          done();
        });
      });
    });
  });
});
```

However, when we run our tests, we get this error:

![Reverse order test fails; expected length 2, received length 4](https://thepracticaldev.s3.amazonaws.com/i/d5s45v2q83hcabds7xme.png)

Our second test failed because our `notes` object store in the `myDatabase` IndexedDB database had the sticky notes from the first test. So how can we make sure for each test, we're only working with the database items from our that test case?

What if we were using a __different IndexedDB database__ for each test? The forward-order test could be running code with the `notes` store for a database named `myDatabase_FORWARD`, while the reverse-order one would use `myDatabase_REVERSE`. This technique of running each database test in a database with a different name is called **namespacing**, and we can namespace our tests with just a couple code changes in `setupDB`.

```javascript
let db;
let dbNamespace;

function setupDB(namespace, callback) {
  if (namespace != dbNamespace) {
    db = null;
  }
  dbNamespace = namespace;

  // If setupDB has already been run and the database was set up, no need to
  // open the database again; just run our callback and return!
  if (db) {
    callback();
    return;
  }
```

We add a new global variable to `db.js`, `dbNamespace`, which is the namespace for the IndexedDB database we are currently using. Then, in `setupDB`, we have a new parameter, `namespace`; if we use a namespace different from what `dbNamespace` was already set to, then we set `db` to null so we will have to open a new IndexedDB database ([commit 4](https://github.com/andyhaskell/indexeddb-tutorial/commit/4235bd7a3734e5faa84ec4cc67626196bb2fa602)).

```javascript
let dbName = namespace == '' ? 'myDatabase' : 'myDatabase_${namespace}';
let dbReq = indexedDB.open(dbName, 2);
```

Now, we pick the name of the database we want to open based on what we passed into `namespace`; if we pass in a non-blank string as our namespace, such as REVERSE_TEST, then we are opeining the database myDatabase_REVERSE_TEST, so if each test uses a different namespace, we won't have to worry about leftover database items from the last test.

Now, our forward getNotes test will start like this:

```javascript
test('we can store and retrieve sticky notes', function(done) {
  setupDB('FORWARD_TEST', function() {
```

Our reverse test looks like:

```javascript
test('reverse order', function(done) {
  setupDB('REVERSE_TEST', function() {
```

And finally, in our web app, we set up the database with no namespace by running:

```javascript
setupDB('', getAndDisplayNotes);
```

With both of our test cases now using databases with different namespaces, one test case doesn't interfere with another, so run `npx jest` and you will see...

![Both tests now pass in the command line](https://thepracticaldev.s3.amazonaws.com/i/0qqu3hsuhxf0nctojcrz.png)

### A PASSING TEST!

![Lola the Micropanda smiling about the test passing](https://thepracticaldev.s3.amazonaws.com/i/nuf3kmtuztobm6jgpk1b.jpg)

We've given our web app test coverage for a couple test cases in Jest using callbacks, and with namespacing in the `setupDB` function, we've got a sustainable way to keep our tests from colliding with each other if we kept on adding features to the app. However, there is still one issue with the codebase, all these pyramids of callbacks can be tough to reason about in our code. So for my next tutorial, we're going to look into how we can take our callback-based IndexedDB code and turn it into promise/`async/await`-based IndexedDB code. Until next time,

![Three-toed sloth climbing a tree](https://thepracticaldev.s3.amazonaws.com/i/h0q0eos1p1wvb0aiut17.jpg)

STAY SLOTHFUL!