typeorm-transactional-async-callbacks
Version:
A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS
103 lines (79 loc) • 3.21 kB
text/typescript
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { User } from './entities/User.entity';
import { UserReaderService } from './services/user-reader.service';
import { UserWriterService } from './services/user-writer.service';
import { initializeTransactionalContext, addTransactionalDataSource, StorageDriver } from '../src';
describe('Integration with Nest.js', () => {
let app: TestingModule;
let readerService: UserReaderService;
let writerService: UserWriterService;
let dataSource: DataSource;
beforeAll(async () => {
const storageDriver =
process.env.TEST_STORAGE_DRIVER && process.env.TEST_STORAGE_DRIVER in StorageDriver
? StorageDriver[process.env.TEST_STORAGE_DRIVER as keyof typeof StorageDriver]
: StorageDriver.CLS_HOOKED;
initializeTransactionalContext({ storageDriver });
app = await Test.createTestingModule({
imports: [
TypeOrmModule.forRootAsync({
useFactory() {
return {
type: 'postgres',
host: 'localhost',
port: 5436,
username: 'postgres',
password: 'postgres',
database: 'test',
entities: [User],
synchronize: true,
logging: false,
};
},
async dataSourceFactory(options) {
if (!options) {
throw new Error('Invalid options passed');
}
return addTransactionalDataSource(new DataSource(options));
},
}),
TypeOrmModule.forFeature([User]),
],
providers: [UserReaderService, UserWriterService],
exports: [],
}).compile();
readerService = app.get<UserReaderService>(UserReaderService);
writerService = app.get<UserWriterService>(UserWriterService);
dataSource = app.get(DataSource);
await dataSource.createEntityManager().clear(User);
});
afterEach(async () => {
await dataSource.createEntityManager().clear(User);
});
afterAll(async () => {
await app.close();
});
it('should create a user using service if transaction was completed successfully', async () => {
const name = 'John Doe';
const onTransactionCompleteSpy = jest.fn();
const writtenPost = await writerService.createUser(name, onTransactionCompleteSpy);
expect(writtenPost.name).toBe(name);
const readPost = await readerService.findUserByName(name);
expect(readPost?.name).toBe(name);
expect(onTransactionCompleteSpy).toBeCalledTimes(1);
expect(onTransactionCompleteSpy).toBeCalledWith(true);
});
it('should fail to create a user using service if error was thrown', async () => {
const name = 'John Doe';
const onTransactionCompleteSpy = jest.fn();
expect(() =>
writerService.createUserAndThrow(name, onTransactionCompleteSpy),
).rejects.toThrowError();
const readPost = await readerService.findUserByName(name);
expect(readPost).toBeNull();
expect(onTransactionCompleteSpy).toBeCalledTimes(1);
expect(onTransactionCompleteSpy).toBeCalledWith(false);
});
});