After our publishing app became more and more complex, we realized that unit tests, those who deal with a particular block of code (such as a function) and mock everything else was not enough. We needed a way to make sure that the way we interact with our database is correct, so we summoned the power of integration tests.
In order to make this possible, we first needed to specify which environment jest
should use for testing by adding the following line to your package.json
:
"jest": {
...
"testEnvironment": "<rootDir>/DatabaseTestEnvironment.js"
},
Then we need to make sure that the default.js
file from the config
folder contains the correct database information so that the Postgres client can connect properly before setting up the test database:
const path = require('path')
const defaultConfig = require('hindawi-review/config/default')
defaultConfig['pubsweet-server'].db = {
database: global.__testDbName || 'postgres',
connectionTimeoutMillis: 5000,
idleTimeoutMillis: 1000,
}
defaultConfig.dbManager.migrationsPath = path.resolve(
__dirname,
'..',
'..',
'app',
'migrations',
)
module.exports = defaultConfig
In our very own custom environment, we’re going to set up a temporary test database and then we’re going to tear it down after all the tests are complete:
const NodeEnvironment = require('jest-environment-node')
const pg = require('pg')
const logger = require('@pubsweet/logger')
const config = require('config')
const dbConfig = config['pubsweet-server'] && config['pubsweet-server'].db
let client
class DatabaseTestEnvironment extends NodeEnvironment {
async setup() {
await super.setup()
client = await new pg.Client(dbConfig)
await client.connect()
// pass the test database name into the test environment as a global
this.global.__testDbName = `test_${Math.floor(Math.random() * 9999999)}`
await client.query(`CREATE DATABASE ${this.global.__testDbName}`)
}
async teardown() {
// terminate other connections from test before dropping db
logger.info(`TEST DB: ${this.global.__testDbName}`)
await client.query(
`REVOKE CONNECT ON DATABASE ${this.global.__testDbName} FROM public`,
)
await client.query(`
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = '${this.global.__testDbName}'`)
await client.query(`DROP DATABASE ${this.global.__testDbName}`)
await client.end()
await super.teardown()
}
}
module.exports = DatabaseTestEnvironment
Once that’s done we can go ahead and run our first test using a connection to a real database:
const models = require('@pubsweet/models')
const { db, migrate } = require('@pubsweet/db-manager')
const { uniq } = require('lodash')
const { getManuscriptsUseCase } = require('../src/use-cases')
describe('get manuscripts use case', () => {
beforeAll(async () => {
await dbCleaner()
await db.seed.run({
directory: '../app/seeds',
})
await db.seed.run({
directory: '../app/testSeeds',
})
})
afterAll(done => {
db.destroy()
done()
})
it('returns all manuscripts if the user is admin', async () => {
const { Team, TeamMember, Manuscript } = models
const { userId } = await TeamMember.findOneByRole({
role: Team.Role.admin,
})
const { manuscripts } = await getManuscriptsUseCase
.initialize({ models })
.execute({ input: {}, userId })
const submissionIds = manuscripts.map(m => m.submissionId)
expect(submissionIds.length).toEqual(uniq(submissionIds).length)
const draftManuscript = manuscripts.find(
m => m.status === Manuscript.Statuses.draft,
)
expect(draftManuscript.version).toEqual('1')
})
})
const dbCleaner = async options => {
await db.raw('DROP SCHEMA public CASCADE;')
await db.raw('CREATE SCHEMA public;')
await db.raw('GRANT ALL ON SCHEMA public TO public;')
await migrate(options)
logger.info('Dropped all tables and ran all migrations')
return true
}