New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Guide / Sample for Testing with Jest #5308
Comments
Same exact problem! Each suite will spin up the connection and start running all the migrations and other setups at the same time as other tests running, causing every test to fail with inconsistent errors like relationship does not exist, etc. |
@dan003400 for now I think we are locked. Currently what i do is not use sync and createConnection for each test suites. And remember to use sync for only one time . You can use npm scripts. If its confusing let me know i am ready to help. I just don't want other to go same trouble i faced. |
@shirshak55 I was able to finally get it working, I had a similar idea last night, finally got something working this AM.
Let me know if this helps! |
actually this is not the problem i faced. |
I was facing issue that i was unable to use same connection in typeorm and needed to establish connection on each test suites. Making tests too slow. |
The problem was every suite was running the schema synchronization at the same time causing lots of issues, with this it will run the drop then sync then tests. Yes, I am creating a new connection on every suite in the beforeAll callback and closing that connection in every afterAll callback. |
ok i get it. I think u can do that more efficiently by this instead of doing env stuff. con.ts
setupDb.ts
in Npm scripts
So current problem is only how to pass connection properly so typeorm only uses 1 connection on multiple jest workers. |
I'm having the same issue. When the connection is created in jest |
Solution for unit test with TypeORM (edited with @sarfata recommendations, thanks!)it's working now, hope it helps you guys and please let me know if there is any improvement. settingspackage.json{
"scripts": {
"test": "jest",
"test:cache": "jest --clearCache",
"test:update": "jest -u",
"test:commit": "jest --bail —-findRelatedTests",
"lint": "tsc --noEmit && eslint 'src/**/*.ts{x,}' --fix --quiet && prettier-check --ignore-path .gitignore \"{.,src/**}/{*.ts{x,},*.md,ts*.json,*.config.js}\"",
},
"dependencies": {
"@drdgvhbh/postgres-error-codes": "0.0.6",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"reflect-metadata": "^0.1.13",
"tslib": "1.11.2",
"typeorm": "^0.2.24",
},
"devDependencies": {
"@types/cookie-parser": "^1.4.2",
"@types/jest": "^25.2.3",
"@types/node": "^13.9.0",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"eslint": "^7.3.1",
"eslint-config-airbnb-typescript": "^7.0.0",
"eslint-config-prettier": "^6.10.0",
"eslint-import-resolver-typescript": "^2.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.17.1",
"eslint-plugin-prettier": "^3.1.3",
"husky": "^4.2.5",
"jest": "^26.1.0",
"lint-staged": "^10.2.2",
"prettier": "^2.0.5",
"prettier-check": "^2.0.0",
"pretty-quick": "^2.0.1",
"ts-jest": "^26.1.1",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.9.5",
},
"lint-staged": {
"**/*.ts{x,}": [
"npm run lint",
"npm run test:commit"
]
}
}
ormconfig.tsimport { ConnectionOptions } from 'typeorm';
import { loadEnv } from './src/libraries/loadEnv';
loadEnv();
const DATABASE_TYPE = 'postgres';
const DATABASE_ENTITIES = ['src/entities/**/**.postgres.ts'];
const connectionOptions: ConnectionOptions[] = [
{
name: 'default',
type: DATABASE_TYPE,
database: String(process.env.DATABASE_DATABASE),
host: String(process.env.DATABASE_HOST),
port: Number(process.env.DATABASE_PORT),
username: String(process.env.DATABASE_USERNAME),
password: String(process.env.DATABASE_PASSWORD),
entities: DATABASE_ENTITIES,
synchronize: true,
// dropSchema: true,
logging: true
},
{
name: 'test',
type: DATABASE_TYPE,
database: String(process.env.DATABASE_DATABASE),
host: String(process.env.DATABASE_HOST),
port: Number(process.env.DATABASE_PORT),
username: String(process.env.DATABASE_USERNAME),
password: String(process.env.DATABASE_PASSWORD),
entities: DATABASE_ENTITIES,
synchronize: true,
dropSchema: true,
logging: false
}
];
export = connectionOptions;
];
export = connectionOptions; src/libraries/loadEnv.tsimport { isProd, isTest } from 'src/constants/Envoriment';
import * as dotenv from 'dotenv';
export const loadEnv = () => {
const loadFile = () => {
if (isProd) return '.env';
if (isTest) return '.env.test';
return '.env.dev';
};
return dotenv.config({ path: loadFile() });
}; jest.config.jsconst { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig.json');
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
clearMocks: true,
maxWorkers: 1,
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
setupFilesAfterEnv: ['<rootDir>/src/test-utils/db-env.ts']
}; src/test-utils/db-env.ts// This file is executed once in the worker before executing each test file. We
// wait for the database connection and make sure to close it afterwards.
import setupServer from 'src/server/server.factory';
process.env.NODE_ENV = 'test';
beforeAll(async () => {
const t0 = Date.now();
const connection = await setupServer.connectionPostgres.create('test');
const connectTime = Date.now();
await connection.runMigrations();
const migrationTime = Date.now();
console.log(
` 👩🔬 Connected in ${connectTime - t0}ms - Executed migrations in ${
migrationTime - connectTime
}ms.`
);
});
afterAll(async () => {
await setupServer.connectionPostgres.close();
}); server.ts connectionPostgres: {
async create(connectionName: 'default' | 'test' = 'default'): Promise<Connection> {
const connectionOptions = await getConnectionOptions(connectionName);
const connection = await createConnection({ ...connectionOptions, name: 'default' });
return connection;
},
async close(): Promise<void> {
await getConnection().close();
},
async clear(): Promise<void> {
const connection = getConnection();
const entities = connection.entityMetadatas;
await Promise.all(
entities.map(async (entity) => {
const repository = connection.getRepository(entity.name);
await repository.query(`DELETE FROM ${entity.tableName}`);
})
);
}
}, Testsrc/entities/Postgres/User/tests/User.test.tsimport setupServer from 'src/server/server.factory';
import User from '../User.postgres';
describe('User entity', function () {
beforeAll(async () => {
await setupServer.connectionPostgres.create();
});
afterAll(async () => {
await setupServer.connectionPostgres.close();
});
it('should be empty', async function () {
const count = await User.count();
expect(count).toBe(0);
});
}); Docker (optional - for development only, never use docker for db)docker-compose.db.yamlversion: '3.3'
services:
postgres:
image: postgres:11
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: db
ports:
- 5432:5432
postgres_tests:
image: postgres:11
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5433:5433
command: -p 5433 |
@guiaramos I believe with your solution each test file will redrop and recreate the database completely. This might take a bit of time. Instead, you can define a This works for me and took me a very long time to figure out. I 👍 this issue, there should be clearer documentation on how to do this. jest.config
setup-db.ts// Those first two require are very important - without them the typescript migrations did not work for me.
// See https://github.com/facebook/jest/issues/10178
// tslint:disable-next-line: no-var-requires
require("ts-node/register")
// tslint:disable-next-line: no-var-requires
require("tsconfig-paths/register")
import "dotenv/config"
import { createConnection } from "typeorm"
import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"
import ormConfig from "../ormconfig"
/*
* This file is executed by Jest before running any tests.
* We drop the database and re-create it from migrations every time.
*/
export default async () => {
// Force dropping the schema so that test run clean every time.
// Note that we are not cleaning *between* tests.
const testOrmConfig: PostgresConnectionOptions = {
...(ormConfig as PostgresConnectionOptions),
dropSchema: true,
}
const t0 = Date.now()
const connection = await createConnection(testOrmConfig)
const connectTime = Date.now()
await connection.runMigrations()
const migrationTime = Date.now()
console.log(
` 👩🔬 Connected in ${connectTime -
t0}ms - Executed migrations in ${migrationTime - connectTime}ms.`
)
} db-env.ts// This file is executed once in the worker before executing each test file. We
// wait for the database connection and make sure to close it afterwards.
import { getConnection } from "typeorm"
import { createDatabaseConnection } from "~/repositories/DBConnection"
beforeAll(async () => {
await createDatabaseConnection()
})
afterAll(async () => {
await getConnection().close()
}) And my import { createConnection } from "typeorm"
import ormConfig from "../ormconfig"
export async function createDatabaseConnection() {
return await createConnection(ormConfig)
} |
@sarfata Great!! Thanks for the improvement I will apply it. |
@guiaramos this is very, very helpful, thank you. Do you have a complete example in a repo? Or, could you share the full code for |
@good-idea nice, I have this repo that I am currently working on: Actually, I have to update my previous comment to this issue according to the repo... anyway you can find a good representation of factory in this example also: |
It is better to have two separate test environments (two test databases), one for testing resolvers (which is closer to an integration test), and the other for testing everything else. When testing graphql resolvers, seeding the database should only be done once as this is a slow operation. Currently, using 'globalSetup` and `globalTeardown` with jest doesn't work for setting up the `typeorm` database. The tests are not able to find the connection. See: #73 typeorm/typeorm#5308 jestjs/jest#10178 It is also not efficient to remove all of the entries for this database in order to start with a clean slate for testing the models and testing the seeding operations. So what we do here is we have two test databases. 1. a prepopulated frozen test database (called `test_db`) to be used by the graphql resolvers 2. an empty database (called `empty_test_db`) which we can safely mutate (like inserting and deleting operation) to test seeding functions, among others So this pull request updates all the scripts and other files for the current tests to work I have also refactored some of the test scripts to be more readable and also added a simple test for testing the `abilityById` graphql query..
@guiaramos @sarfata were you able to specify
|
@ginachoi not using seeds - sorry. |
@ginachoi sorry for late reply, I am not using seeds but recommend this lib https://github.com/w3tecch/typeorm-seeding |
What about that : |
@BenGrandin this solution works but the migrations will be executed once for each test file. If you have many test files, this slows down everything considerably. |
The sample needed for jest is to be able to mock the entity manager in a 'unit test'- in particular using transactions. We are able to mock everything but this. The sample needed is one where a database is not required. |
Hi, A common suggestion for TypeORM tests that use the database is to serialize them (via Jest options I recently was able to parallelize such tests by running multiple databases. The idea is to run each Jest worker with its own database. Tests within one worker run serially but each worker runs in parallel to each other with its own database. This is quite easy to setup using Docker and Jest and cut my build / test times in half. Here is a write up: |
We just mock out the connection (and typeorm doesn’t make that easy) - it’s an anti pattern to require a database for unit tests. Typeorm biggest downfall to me is the lack of good testing support in the library |
@sgentile I generally have two sets of tests in my project. You might call the first set "unit tests" and they mock most things out (including the database). You might call the second set "integration tests" (or something else) and they do not mock most things out (including the database). I think both are highly valuable. My approach above applies to the latter - any tests (whatever you call them) that use the database. |
Yes that is why I said “unit tests”. This is where typeorm can be painful to mock out especially dealing with transactions |
I really hope that it becomes more and more testable 😞 |
Is it possible to create a DB connection only once when running all the test files? What I'm trying to achieve is to
Now I have the following files:
Although it works, it's slow because a DB connection is created for each test file. So, I updated
Then, the following file was added:
Then, I commented out
However, I started to see the following error:
The error message comes from Does this mean it's necessary to create a DB connection for each test file? |
@Hiroki111 You should store the connection in a global variable in the globalSetup export default async () => {
const connection = await createConnection(testDbConfig);
await connection.runMigrations();
(globalThis as any).connection = connection;
}; globalTeardown export default async () => {
const connection = (globalThis as any).connection;
await connection.close();
}; |
Thank you for your suggestion, but I'd avoid storing the DB connection to e.g.
If
This may work, but there are many functions like this, so it'll be best NOT to save the DB connection and share it across files |
It would be great to have a guide to using jest and typeorm together.
This guide would ideally have patterns for both using real connections for integration testing as well as mocking the TypeORM connection / repository / etc entirely.
Currently, if we mock
jest.mock
TypeORM the entity definitions stop working as expected.Original Issue Description Below
Issue type:
[ X ] question
[ ] bug report
[ X ] feature request
[ ] documentation issue
Database system/driver:
[ ]
cordova
[ ]
mongodb
[ ]
mssql
[ ]
mysql
/mariadb
[ ]
oracle
[ X]
postgres
[ ]
cockroachdb
[ ]
sqlite
[ ]
sqljs
[ ]
react-native
[ ]
expo
TypeORM version:
[ X]
latest
[ ]
@next
[ ]
0.x.x
(or put your version here)Is there any standard way to test typeorm based model etc.? I don't mind direct connection to database for integration testing (especially mocking gets too complex)
I setup global connection like this.
Then I tried to use global setup/ different environment calling setupConn function but the problem is:
Jest spawns worker for each test suites (different *.test.js) file.
So I always see this error
but if i use same function at beforeAll at test suites it works for 1 test suite .
So is there any good and recommend way to test application developed using typeorm.
And thanks for making such a beautiful orm I really enjoy using it :)
The text was updated successfully, but these errors were encountered: