dwyl / book

📗 Our Book on Full-Stack Web Application Development covering User Experience (UX) Design, Mobile/Offline/Security First, Progressive Enhancement, Continuous Integration/Deployment, Testing (UX/TDD/BDD), Performance-Driven-Development and much more!
https://dwyl.github.io/book/
820 stars 134 forks source link

How to reset test database before each test run? #44

Open tamlyn opened 9 years ago

tamlyn commented 9 years ago

In order to have repeatable tests it seems a good idea to have a test database populated with known data against which to write the tests. Populating this database is straightforward and resetting it could be as simple as dropping it at the end of the test. But adding an automated drop database to my test scripts seems like a very bad idea since a config error could easily result in dropping a production database. Any suggestions? I'm using MongoDB and Mocha/Karma/Protractor.

nelsonic commented 9 years ago

Good question.

When we've done MongoDB-based apps we create the db in the first test (usually named _setup.js or _db.js (the underscore in the file name is the important bit because the test runner opens files alphabetically).

Then drop the database with a db.dropDatabase() call at the end of our test suite (usually in a file called z_teardown.js again for alphabetical reasons).

see: http://docs.mongodb.org/manual/reference/command/dropDatabase/ Here's an example, but using ElasticSearch: https://github.com/dwyl/api/tree/master/test

Note: you may need to use node-mongodb-native run command to create the DB. But Mongoose should auto-create the DB when a record is first inserted into Mongo: http://stackoverflow.com/questions/16459286/how-to-create-a-new-db-in-mongoose

We usually create methods specific to our project and export them as a module e.g: your-project-name/test/_test_helpers.js e.g: https://github.com/dwyl/api/blob/master/test/_test_helpers.js so we can include the module in any file while testing a single file/test.

Let us know if that answers your question. Thanks!

tamlyn commented 9 years ago

Thanks.

Actually by default Mocha runs files in whatever order the filesystem gives them which is essentially random. You have to specify the -S flag to sort files. However a better solution is to use global before and after hooks and not worry about execution order.

You also gave me the idea of only deleting the database if it didn't exist in the first place. That way we can be sure not to delete anything important.

var mongoose = require('mongoose');
var fixtures = require('../../server/fixtures');

//should we delete the database after the test run?
var deleteAfterRun = false;

//run once before all tests
before(function (done) {

    //test if database is populated
    var User = mongoose.model('User');
    User.count({})
        .then(function (count) {
            if (count === 0) {
                //no content so safe to delete
                deleteAfterRun = true;
                //add test data
                return fixtures.ensureTestData();
            } else {
                console.log('Test database already exists');
            }
        })
        .then(function() {
            done();
        });

});

//run once after all tests
after(function (done) {
    if (deleteAfterRun) {
        console.log('Deleting test database');
        mongoose.connection.db.dropDatabase(done);
    } else {
        console.log('Not deleting test database because it already existed before run');
        done();
    }
});
nelsonic commented 9 years ago

@tamlyn Sweet! that's a much cleaner solution (for Mocha) We went Mocha > Lab > Tape > QUnit (our current preferred testing framework)

maksnester commented 7 years ago

Hey guys, since 2 years, does something changed? I mean, now I also trying to test some of models and api in mongo/mongoose based app, so tests need to have running database. I wrote some helper module for tests which looks like

/*
Before running tests which requires database connection could be useful to
0) init db connection
1) ensure that we have connection
2) clean database
 */

const mongoose = require('mongoose')
require('dotenv').config()
require('../initdb')() // here actual connection happens, also here some reconnect logic for app, so module used by app

module.exports = {
  ensureConnection() {
    return new Promise((resolve) => {
      if (mongoose.connection.db) {
        return resolve(mongoose.connection.db)
      }
      mongoose.connection.on('connected', () =>
        resolve(mongoose.connection.db)
      )
    })
  },

  cleanDatabase() {
    return mongoose.connection.db.dropDatabase()
  }
}

Generally, in tests, I want to drop db in first before block for suite (main describe in test file I mean) and in last after block. What I concerned about: is there some guaranties that test files never be run by mocha in parallel?

How do you test your code in 2017?

BudgieInWA commented 6 years ago

In 2018, acceptance tests (e.g. with jest and jest-puppeteer) can run meteor with meteor test --full-app --driver-package tmeasday:acceptance-test-driver --settings test-settings.json --port 1234 then hit http://localhost:1234 to drive their tests. This starts meteor in a fresh state each time with Meteor.isTest === true so your fixtures can do their thing.

Unit tests should use in memory collection mocks so that they are isolated from one another.

Integration tests that involve the database probably want to test the production techniques anyway.