-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(testing): Modularize DB support for e2e tests
Relates to #207. This change decouples the TestServer from the underlying database, allowing custom TestDbInitializers to be created for other TypeORM-supported DBs. BREAKING CHANGE: The `@vendure/testing` package now requires you to explicitly register initializers for the databases you with to test against. This change enables e2e tests to be run against any database supported by TypeORM. The `dataDir` option has been removed from the call to the `TestServer.init()` method, as it is specific to the SqljsInitializer: before: ```TypeScript import { createTestEnvironment, testConfig } from '@vendure/testing'; describe('my e2e test suite', () => { const { server, adminClient } = createTestEnvironment(testConfig); beforeAll(() => { await server.init({ dataDir: path.join(__dirname, '__data__'), initialData, productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), customerCount: 1, }); }); //... }); ``` after: ```TypeScript import { createTestEnvironment, registerInitializer, SqljsInitializer, testConfig } from '@vendure/testing'; registerInitializer('sqljs', new SqljsInitializer(path.join(__dirname, '__data__'))); describe('my e2e test suite', () => { const { server, adminClient } = createTestEnvironment(testConfig); beforeAll(() => { await server.init({ initialData, productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), customerCount: 1, }); }); //... }); ```
- Loading branch information
1 parent
50bdbd8
commit f8060b5
Showing
9 changed files
with
260 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { ConnectionOptions } from 'typeorm'; | ||
|
||
import { TestDbInitializer } from './test-db-initializer'; | ||
|
||
export type InitializerRegistry = { [type in ConnectionOptions['type']]?: TestDbInitializer<any> }; | ||
|
||
const initializerRegistry: InitializerRegistry = {}; | ||
|
||
/** | ||
* @description | ||
* Registers a {@link TestDbInitializer} for the given database type. Should be called before invoking | ||
* {@link createTestEnvironment}. | ||
* | ||
* @docsCategory testing | ||
*/ | ||
export function registerInitializer(type: ConnectionOptions['type'], initializer: TestDbInitializer<any>) { | ||
initializerRegistry[type] = initializer; | ||
} | ||
|
||
export function getInitializerFor(type: ConnectionOptions['type']): TestDbInitializer<any> { | ||
const initializer = initializerRegistry[type]; | ||
if (!initializer) { | ||
throw new Error(`No initializer has been registered for the database type "${type}"`); | ||
} | ||
return initializer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import path from 'path'; | ||
import { ConnectionOptions } from 'typeorm'; | ||
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'; | ||
import { promisify } from 'util'; | ||
|
||
import { TestDbInitializer } from './test-db-initializer'; | ||
|
||
export class MysqlInitializer implements TestDbInitializer<MysqlConnectionOptions> { | ||
private conn: import('mysql').Connection; | ||
|
||
async init( | ||
testFileName: string, | ||
connectionOptions: MysqlConnectionOptions, | ||
): Promise<MysqlConnectionOptions> { | ||
const dbName = this.getDbNameFromFilename(testFileName); | ||
this.conn = await this.getMysqlConnection(connectionOptions); | ||
(connectionOptions as any).database = dbName; | ||
(connectionOptions as any).synchronize = true; | ||
const query = promisify(this.conn.query).bind(this.conn); | ||
await query(`DROP DATABASE IF EXISTS ${dbName}`); | ||
await query(`CREATE DATABASE IF NOT EXISTS ${dbName}`); | ||
return connectionOptions; | ||
} | ||
|
||
async populate(populateFn: () => Promise<void>): Promise<void> { | ||
await populateFn(); | ||
} | ||
|
||
async destroy() { | ||
await promisify(this.conn.end).bind(this.conn)(); | ||
} | ||
|
||
private async getMysqlConnection( | ||
connectionOptions: MysqlConnectionOptions, | ||
): Promise<import('mysql').Connection> { | ||
const { createConnection } = await import('mysql'); | ||
const conn = createConnection({ | ||
host: connectionOptions.host, | ||
port: connectionOptions.port, | ||
user: connectionOptions.username, | ||
password: connectionOptions.password, | ||
}); | ||
const connect = promisify(conn.connect).bind(conn); | ||
await connect(); | ||
return conn; | ||
} | ||
|
||
private getDbNameFromFilename(filename: string): string { | ||
return 'e2e_' + path.basename(filename).replace(/[^a-z0-9_]/gi, '_'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import path from 'path'; | ||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; | ||
|
||
import { TestDbInitializer } from './test-db-initializer'; | ||
|
||
export class PostgresInitializer implements TestDbInitializer<PostgresConnectionOptions> { | ||
private client: import('pg').Client; | ||
|
||
async init( | ||
testFileName: string, | ||
connectionOptions: PostgresConnectionOptions, | ||
): Promise<PostgresConnectionOptions> { | ||
const dbName = this.getDbNameFromFilename(testFileName); | ||
(connectionOptions as any).database = dbName; | ||
(connectionOptions as any).synchronize = true; | ||
this.client = await this.getPostgresConnection(connectionOptions); | ||
await this.client.query(`DROP DATABASE IF EXISTS ${dbName}`); | ||
await this.client.query(`CREATE DATABASE ${dbName}`); | ||
return connectionOptions; | ||
} | ||
|
||
async populate(populateFn: () => Promise<void>): Promise<void> { | ||
await populateFn(); | ||
} | ||
|
||
destroy(): void | Promise<void> { | ||
return this.client.end(); | ||
} | ||
|
||
private async getPostgresConnection( | ||
connectionOptions: PostgresConnectionOptions, | ||
): Promise<import('pg').Client> { | ||
const { Client } = require('pg'); | ||
const client = new Client({ | ||
host: connectionOptions.host, | ||
port: connectionOptions.port, | ||
user: connectionOptions.username, | ||
password: connectionOptions.password, | ||
database: 'postgres', | ||
}); | ||
await client.connect(); | ||
return client; | ||
} | ||
|
||
private getDbNameFromFilename(filename: string): string { | ||
return 'e2e_' + path.basename(filename).replace(/[^a-z0-9_]/gi, '_'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import { SqljsConnectionOptions } from 'typeorm/driver/sqljs/SqljsConnectionOptions'; | ||
|
||
import { Mutable } from '../types'; | ||
|
||
import { TestDbInitializer } from './test-db-initializer'; | ||
|
||
export class SqljsInitializer implements TestDbInitializer<SqljsConnectionOptions> { | ||
private dbFilePath: string; | ||
private connectionOptions: SqljsConnectionOptions; | ||
constructor(private dataDir: string) {} | ||
|
||
async init( | ||
testFileName: string, | ||
connectionOptions: SqljsConnectionOptions, | ||
): Promise<SqljsConnectionOptions> { | ||
this.dbFilePath = this.getDbFilePath(testFileName); | ||
this.connectionOptions = connectionOptions; | ||
(connectionOptions as Mutable<SqljsConnectionOptions>).location = this.dbFilePath; | ||
return connectionOptions; | ||
} | ||
|
||
async populate(populateFn: () => Promise<void>): Promise<void> { | ||
if (!fs.existsSync(this.dbFilePath)) { | ||
const dirName = path.dirname(this.dbFilePath); | ||
if (!fs.existsSync(dirName)) { | ||
fs.mkdirSync(dirName); | ||
} | ||
(this.connectionOptions as Mutable<SqljsConnectionOptions>).autoSave = true; | ||
(this.connectionOptions as Mutable<SqljsConnectionOptions>).synchronize = true; | ||
await populateFn(); | ||
(this.connectionOptions as Mutable<SqljsConnectionOptions>).autoSave = false; | ||
} | ||
} | ||
|
||
destroy(): void | Promise<void> { | ||
return undefined; | ||
} | ||
|
||
private getDbFilePath(testFileName: string) { | ||
// tslint:disable-next-line:no-non-null-assertion | ||
const dbFileName = path.basename(testFileName) + '.sqlite'; | ||
const dbFilePath = path.join(this.dataDir, dbFileName); | ||
return dbFilePath; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { ConnectionOptions } from 'typeorm'; | ||
import { BaseConnectionOptions } from 'typeorm/connection/BaseConnectionOptions'; | ||
|
||
/** | ||
* @description | ||
* Defines how the e2e TestService sets up a particular DB to run a single test suite. | ||
* The `\@vendure/testing` package ships with initializers for sql.js, MySQL & Postgres. | ||
* | ||
* Custom initializers can be created by implementing this interface and registering | ||
* it with the {@link registerInitializer} function: | ||
* | ||
* @example | ||
* ```TypeScript | ||
* export class CockroachDbInitializer implements TestDbInitializer<CockroachConnectionOptions> { | ||
* // database-specific implementation goes here | ||
* } | ||
* | ||
* registerInitializer('cockroachdb', new CockroachDbInitializer()); | ||
* ``` | ||
* | ||
* @docsCategory testing | ||
*/ | ||
export interface TestDbInitializer<T extends BaseConnectionOptions> { | ||
/** | ||
* @description | ||
* Responsible for creating a database for the current test suite. | ||
* Typically, this method will: | ||
* | ||
* * use the testFileName parameter to derive a database name | ||
* * create the database | ||
* * mutate the `connetionOptions` object to point to that new database | ||
*/ | ||
init(testFileName: string, connectionOptions: T): Promise<T>; | ||
|
||
/** | ||
* @description | ||
* Execute the populateFn to populate your database. | ||
*/ | ||
populate(populateFn: () => Promise<void>): Promise<void>; | ||
|
||
/** | ||
* @description | ||
* Clean up any resources used during the init() phase (i.e. close open DB connections) | ||
*/ | ||
destroy(): void | Promise<void>; | ||
} |
Oops, something went wrong.