Easy Node Postgres fixtures with tiny-fixtures

Easy Node Postgres fixtures with tiny-fixtures

Because integration testing against a database should be easy and repeatable

Years ago, I used to work almost exclusively with a Python web framework called Django. It heavily borrowed from Ruby on Rails, and one of the things I liked about it was how easily it allowed you to setup test data in a database for your tests. All you had to do was specify some JSON, and it inserted this data into the database before each test case, then cleaned it up afterwards.

You could even generate this data programmatically from data in the database via the Django command line interface tools. What a breeze!

Then I came to TypeScript and found nothing quite so easy to use and reliable for database fixtures. Instead we wrote our own insert and delete functions for our tests, and had to keep track of inserted IDs if we wanted to clean up without nuking our local database. Not only was this a lot of really pointless code to write, it's easy to make mistakes too. Sometimes you might run the test suite multiple times and the database gets into an unexpected state, causing tests to fail because something wasn't cleaned up properly. Yuck!

I couldn't keep working like this and decided to create a library to make database fixtures easy. That library is tiny-fixtures.

How tiny-fixtures works

The premise is pretty simple, you supply tinyFixtures a node-postgres connection pool, and it returns a createFixtures function.

You then use that function to, well, create fixtures! You supply it the name of the table and an array of objects to insert. It gives you back an array containing a setup function, a teardown function, and the array of objects you want to insert with some helper functions attached.

The objects in the array that you supply createFixtures should be plain javascript objects. These represent the rows you want inserted, where the keys are the column names and the values are the values for that column! Easy right?

If you need to insert data in another table, and reference the first table by foreign key, then you call createFixtures again, but this time you reference the foreign key value by calling a lookup function on the associated object from the first createFixtures call.

An example is in order! In this example we are addressing the more complicated use case of inserting to two tables, one of which has a one to many relationship with the other.

  describe('Two table with join use case', () => {
    const [setupUserFixtures, teardownUserFixtures, users] = createFixtures('users', [
      {
        email: 'foo@bar.co',
        username: 'tinyAnt'
      }, {
        email: 'bar@foo.co',
        username: 'antTiny',
      }
    ]);
    const [setupUserMessageFixtures, teardownUserMessageFixtures] = createFixtures(
      'user_messages',
      [
        {
          user_id: users[0].getRefByKey('id'),
          message: 'Foobar did the bar foo good',
        },
        {
          user_id: users[0].getRefByKey('id'),
          message: 'I am a meat popsicle',
        }
      ],
    )
    beforeEach(async () => {
      await setupUserFixtures();
      await setupUserMessageFixtures();
    });
    afterEach(async () => {
      await teardownUserMessageFixtures();
      await teardownUserFixtures();
    });
    it('should have a user with two messages, and one with none', async () => {
      const users = await getUsers();
      expect(users.length).to.equal(2);
      expect(users[0].messages.length).to.equal(2);
      expect(users[1].messages.length).to.equal(0);
    });
  });

All we had to do was specify our test data, and then call our setup and teardown functions in the before, beforeEach, after, or afterEach hooks as appropriate. Now the code I'm writing for my integration tests directly adds value to my tests, rather than test specific setup and teardown helper functions or even mocks and stubs.

So user_id: users[0].getRefByKey('id'), might look a bit weird, but that's because when you are setting up your fixtures, the sequence values from the database don't exist yet, and could be different for every test case. Instead getRefByKey takes a string containing the name of a column that you want from the row at runtime, and will look that column up each time it inserts a row.

How did I write this library?

The first step I took when I realised I wanted to use this library was actually writing the docs. I wrote basic documentation for a library I wish I had, and then shared it amongst my peers, asking if they would use it. I received a lot of emphatic yes's, some with heavy sighs and some with a little begging. I took that as a good sign and started writing my library.

Now I have a basic use case covered, I'm waiting to see if it gets traction before I add any further enhancements.

Some improvements I'm considering are:

  • A command line tool to pull data out of a database and save it as JSON for tiny-fixtures.
  • The ability to read JSON files as fixtures
  • Saving a log of inserts to a file so that test runs with errors that prevent cleanup are recovered from seamlessly in subsequent runs.
  • MySQL and other database support, if requested.

Have a play and let me know what you think of tiny-fixtures in the comments!