3

To perform unit testing on a mysql database using sequelize as ORM, I need to refresh my database when each test start to run. Actually I wrote a solution like this:

beforeEach(() => {
   table1.destroy({ where: {} })
   table2.destroy({ where: {} })
   table3.destroy({ where: {} })
})

but every time I create tables I have to add another instruction. I would implement a single instruction to perform a complete wipe of the entire schema

Something like:

beforeEach(() => db.clean())
gio
  • 801
  • 3
  • 15
  • 27

2 Answers2

4

sequelize.truncate({ cascade: true, restartIdentity: true })

This is documented at: https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-method-truncate this truncates all tables, which seems the closest to what you want:

Truncate all tables defined through the sequelize models. This is done by calling Model.truncate() on each model.

and for the option argument:

The options passed to Model.destroy in addition to truncate

Parallel testing can then be handled with one of the techniques mentioned at: https://sqa.stackexchange.com/questions/16854/designing-database-reliant-tests-for-parallel-execution/47244#47244 Will's mocking suggestion also solves the parallel issue, so might be worth looking into as well.

The {truncate: cascade} is required when foreign keys are involved, otherwise PostgreSQL 13.4 complains with:

cannot truncate a table referenced in a foreign key constraint

{truncate: cascade} makes it run TRUNCATE "Mytable" CASCADE instead of just TRUNCATE "MyTable", related:

The {restartIdentity: true} can help make tests more reproducible by also resetting the primary key counter: How to reset autoIncrement primary key with sequelize? But note that it is broken on SQLite: https://github.com/sequelize/sequelize/issues/13286

Minimal runnable example:

const assert = require('assert');
const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'db.sqlite',
});
const IntegerNames = sequelize.define(
  'IntegerNames', {
  value: { type: DataTypes.INTEGER, allowNull: false },
  name: { type: DataTypes.STRING, },
});
const IntegerNames2 = sequelize.define(
  'IntegerNames2', {
  value: { type: DataTypes.INTEGER, allowNull: false },
  name: { type: DataTypes.STRING, },
});

(async () => {
// Create and populate databases.
await IntegerNames.sync({force: true})
await IntegerNames.create({value: 2, name: 'two'});
await IntegerNames.create({value: 3, name: 'three'});
await IntegerNames2.sync({force: true})
await IntegerNames2.create({value: 4, name: 'four'});
await IntegerNames2.create({value: 5, name: 'five'});

// Check that they were populated.
assert((await IntegerNames.findAll()).length === 2);
assert((await IntegerNames2.findAll()).length === 2);

// Truncate them and check that they are empty.
await sequelize.truncate({ cascade: true, restartIdentity: true });
assert((await IntegerNames.findAll()).length === 0);
assert((await IntegerNames2.findAll()).length === 0);

// Cleanup.
await sequelize.close();
})();

When we run this, Sequelize logs the following two DB lines among others:

Executing (default): DELETE FROM `IntegerNames2s`
Executing (default): DELETE FROM `IntegerNames`

which seems to be its version of the TRUNCATE statement according to: https://sqlite.org/lang_delete.html#the_truncate_optimization

Tested on 6.5.1, Node v14.16.0.

Another testing approach: SQLite in-memory database

For testing on SQLite, this is a good approach that ensures that everything is completely clean each time, you don't even have to worry about truncating or creating unique database names

new Sequelize({
  dialect: 'sqlite',
  storage: ':memory:',
})
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
-3

You should not touch the database when doing unit testing.

If you're testing business logic that talks to sequelize, create a mock of the interface to sequelize and inject that into the unit being tested. You can then make assertions or expectations with regard to calls to the mock interface's methods. Without more details of the testing environment, it's impossible to provide more concrete direction than that, but you might investigate the sequelize-mocking package to facilitate that.

If you're testing that sequelize is actually talking to your database, then you are doing a whole lot more than unit testing, and I would argue that you would want an out-of-band way of initializing and managing the environment, but I'd also point out that sequelize has a whole suite of tests already.

Will
  • 2,163
  • 1
  • 22
  • 22
  • You definitely should perform tests with a real database, especially nowadays when setting up a database is made easy with containers. See https://stackoverflow.com/questions/928760/how-to-simulate-a-db-for-testing-java – Eyal Roth Jun 29 '21 at 11:37
  • @EyalRoth that doesn't make sense in the context of this question. Yes, you should have some sort of integration/e2e tests to check that systems are working together as expected. But the OP is writing unit tests and trying to reset the entire database between each test. This is clearly a situation where mocking is more appropriate. – Will Jun 30 '21 at 15:22
  • I would argue that you can and should do it with unit test, but that's not really the point. Integration and e2e tests are often written in the same testing frameworks as unit tests, and they often require a "reset" between tests just like unit tests do, so a solution to truncate all the tables is relevant still. – Eyal Roth Jul 01 '21 at 14:13
  • I'll concede that truncating tables in an e2e test is valid, but that's is not relevant in this question. I believe the question is of the XY variety (https://meta.stackexchange.com/a/66378). I would argue fervently against you on what you think isn't really the point, because it absolutely is the point. Unit tests should cover the unit under test, the whole unit, and nothing but the unit. Now, if a real thing runs faster than a mock, use the real thing, but the overhead of using even a light real database is going to quickly add up and deliver an embarrasingly slow test suite. – Will Jul 03 '21 at 23:28
  • I came to this question by googling "sequelize truncate all tables" or something similar. I did not even use the term "test" in my search. The title also focuses on how to wipe all the tables rather than how to model unit tests. IMHO, this question is not about unit testing, but about truncating all data from a schema using Sequelize. To be fair, I still believe that this does not answer the problem of resetting the DB between tests (unit or e2e) since this solution will truncate static migrations data (which should not be deleted) as well. – Eyal Roth Jul 04 '21 at 09:54