Skip to content
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

Rollback database in e2e tests #1843

Closed
grigori-gru opened this issue Mar 25, 2019 · 6 comments
Closed

Rollback database in e2e tests #1843

grigori-gru opened this issue Mar 25, 2019 · 6 comments

Comments

@grigori-gru
Copy link

I'm submitting a...


[ ] Regression 
[ ] Bug report
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

All database should be cleaned, reloaded and seed before each e2e test. If have many tests you spend too much time.

Expected behavior

Each test with database is never committed, all data becomes as initial after each. Database creates and seed only ones before all tests.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

Some other frameworks have good practice to roll back all DB transactions inside test case after each test is finished. It lets you not to spend too much time for clean and reload all your DB and seed testing fixtures. You just load your test database, seed data before all tests and clean it after finish optionally but all your tests are independent and have the same environment.
I can't find it in docs or issues, even DB transactions at least. Maybe you have something for Typeorm?

Environment


Nest version: 6.0.0

 
Others:

@quezak
Copy link

quezak commented Mar 29, 2019

I solved it using a separate db for e2e tests. When starting e2e tests, drop the db and re-add all migrations, including a "test migration" which adds some data. For e2e tests I'm using the my whole app module, just overriding the typeorm config factory (for this to work, my normal case needs to use forRootAsync with a factory too).

export class TypeormE2EConfigService implements TypeOrmOptionsFactory {
    createTypeOrmOptions(): PostgresConnectionOptions {
        return {
            ... my usual local DB connection options
            // Use a separate database, where data can be safely dropped each time
            database: 'test',
            // We want a clean database state, but need some of the dev data to test swaps
            dropSchema: true,
            synchronize: false,
            migrationsRun: true,
            migrations: [
                'src/models/migrations/*.ts',
                'src/models/testMigrations/*.ts',
            ],
        };
    }
}

...
// test app initialization
    const moduleFixture = await Test
        .createTestingModule({ imports: [AppModule] })
        .overrideProvider(TypeormConfigService)  // <- this is my "regular" typeorm options factory
        .useValue(typeormOptionsFactory)
        .compile();

@BrunnerLivio
Copy link
Member

@grigori-gru
Copy link
Author

Thanks! But both your comments is near to reload your seeding data and restart database in each test case even inside one test file. When I run each my test file in beforeAll part, I just use my orm configurations which depends on test env - it always start my database with dropSchema and synchronyze options as true:

    get dbParams(): TypeOrmModuleAsyncOptions {
        const SOURCE_PATH = process.env.NODE_ENV === 'production' ? 'dist' : 'src';

        return {
            type: 'postgres',
            host: this.envConfig.db.host,
            port: this.envConfig.db.port,
            username: this.envConfig.db.user,
            password: this.envConfig.db.password,
            database: this.envConfig.db.name,
            synchronize: process.env.NODE_ENV === 'test',
            dropSchema: process.env.NODE_ENV === 'test',
            entities: [`${SOURCE_PATH}/**/**.entity{.ts,.js}`],
        } as TypeOrmModuleAsyncOptions;
    }

It lets me reload my test data each time. As I've seen you seed data using test migration or using test fixtures. But my question was about using rollback transaction in every test inside file. Somehow I should give to my app module in tests database which have been started with queryRunner. After each I roll it back:

beforeAll(() => {
    // Here I start my app with testing data and database with startTransaction()
})

afterEach(async () => {
      await database.rollbackTransaction();
})

it('Should update', async () => {
      await request(app.getHttpServer())
            .post('/users')
            // some data to set
            .expect(201);
      expect(await userService.findAll()).toEqual(//some result);
});

it('Should update again', async () => {
      await request(app.getHttpServer())
            .post('/users')
            // some another data to set
            .expect(201);
      expect(await userService.findAll()).toEqual(//some result without data from test before);
});

In all this tests I don't have to restart all my service and database, I just rollback transactions.
Hope it explains my point better.

@kamilmysliwiec
Copy link
Member

Please, use StackOverflow for such questions.

@adrianbudzynski
Copy link

adrianbudzynski commented Aug 4, 2019

I would like to share with you my workaround for this problem. It is not very clean but it works (in my case). As long as we don't have any built-in option, using below code you are able to rollback all MySQL transactions with db changes that were created during e2e tests.

    let app: INestApplication;
    let fixture: TestingModule;
    let queryRunner: QueryRunner;

    beforeAll(async () => {
        fixture = await Test.createTestingModule({
            imports: [AppModule],
        })
            .compile();
        app = fixture.createNestApplication();
        app.useGlobalPipes(new ValidationPipe());
        await app.init();

        const dbConnection = fixture.get(Connection);
        const manager = fixture.get(EntityManager);
        // @ts-ignore
        queryRunner = manager.queryRunner = dbConnection.createQueryRunner('master');
    });

    beforeEach(async () => {
        await queryRunner.startTransaction();
    });

    afterEach(async () => {
        await queryRunner.rollbackTransaction();
    });

   it('should create new email address subscription', () => {
        return request(app.getHttpServer())
            .post(URL)
            .send({email: 'john.doe@owwly.com'})
            .expect(201);
    });

@lock
Copy link

lock bot commented Nov 2, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Nov 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants