Node — Seed Test DB

Sergey Radzishevskii
3 min readOct 4, 2019

Problem

Let’s take a very common example when we have 1:M relation between 2 tables.

And we need to test postsupdates. Let’s imagine how our test would look in the simplest version.

We need to seed some initial data - fixtures. So we can test updates of the existing post. But the problem is —when our table grows we need to add more initialization code.

The other thing is the related tables. Basically we don’t needusersrecord to be presented in the DB for our test. But the foreign key in the posttable requires users record. Of course, we can disable foreign key checks but it’s not always simple and foreign key checks guarantee data integrity and it can be useful even during testing.

Finally, imagine we have to 50+ tests that insert initial data to the posts table and then we need to add another non-nullable field to the post. And now we need to update 50 tests with {my_new_field: "dummy_value"}

Solution

To simplify our lives let’s build a generator function that will build mock test data.

await knex("posts").insert(DataGenerator.user({id: 2, title: "Title 2"}))

we have less code but more importantly — we hide inessential details. We need to test titleupdates. We only need id and title we don’t need rest. In our first example,

{ id: 2, user_id: 1, title: "Post Title", body: "body"}

these extra fields distract our attention from the main thing. In the real world test, you will have many more fields.

Ok here’s a simple version of data generator for posts.

We used faker to add some entropy to our data. Now we can easily add new fields to posts and don’t care about changes in the tests required post. We can add an extra field in the DataGenerator and that’s it.

https://gist.github.com/radzserg/1c366c01b9124b2e164ccf2b5f5e41f5

Ok looks better but… still, we have a lot of wrapped code — knex, generator they still distract. As I mentioned above we only need to make sure that post with certain id and title exists. So how we can make it even simpler and clear?

Long story short… in order to simplify our lives, I have built

To insert post with all related records, we need to do only

const post = await dbSeeder.insert("post");

Magic… :)

But that’s magic requires some setup

This package doesn’t require specific DB adapter (knex/typeorm/pg) so you will need to build your own, implementing IStorageWriter . Here’s a simple implementation for pg adapter.

Then we need to set up our factories for each record that you need.

user: ref(“user”) will tell rs-db-seeder that this another factory. After that when we do

const post = await dbSeeder.insert(“post”);

dbSeeder will find the referenced column and insert it first.

Full test example

In comparison with our first solution:

  • the test doesn’t have wrapping/extra logic that we don’t need. So helps to focus on the testing scenario.
  • we don’t care about related records, but data integrity checks still work

--

--

Sergey Radzishevskii

Enjoy turning complex systems into intelligible architectures chilling in my garden.