typestack / typeorm-typedi-extensions

Dependency injection and service container integration with TypeORM using TypeDI library.
MIT License
262 stars 37 forks source link

Better way of mocking the injected repository #49

Open jasonlimantoro opened 3 years ago

jasonlimantoro commented 3 years ago

Function getRepository from src/decorators/InjectRepository.ts seems to be problematic. I dealt with it like this:

// UserRepository.ts
import { EntityRepository, Repository } from 'typeorm';

@Service()
@EntityRepository(User)
export class UserRepository extends Repository<User> {  }
// UserResolver.ts
import { Service } from 'typedi';
import { InjectRepository } from 'typeorm-typedi-extensions';

@Service()
export class UserResolver {
    @InjectRepository()
    private readonly userRepository!: UserRepository;
}
// UserResolver.test.ts
import { ConnectionManager } from 'typeorm';

// mocked repository
class MockUserRepository {
    static findOne: jest.MockedFunction<typeof UserRepository.prototype.findOne>;
    static setupMocks() {
        this.findOne = jest.fn().mockResolvedValue(undefined);
    }
}
describe('UserResolver class', () => {
    beforeAll(() => {
        // This, as we know, is not enough for mocking @InjectRepository()
        // Container.set(UserRepository, MockUserRepository);

        // this will be used only once during the initial import so there is no need to put this in beforeEach
        Container.set(ConnectionManager, {
            has: (connectionName: string) => true,
            get: (connectionName: string) => ({
                getRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getMongoRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getTreeRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getCustomRepository: (repositoryType: any) => {
                    switch (repositoryType) {
                        case UserRepository:
                            return MockUserRepository; // here we mock our repository
                        default:
                            console.warn(`No mock repository found for ${repositoryType}`);
                    }
                },
            }),
        });
    });
    beforeEach(() => {
        MockUserRepository.setupMocks();
    });
    it('should pass', async () => {
        MockUserRepository.findOne.mockResolvedValue({});
        await <test>
        expect(MockUserRepository.findOne).toHaveBeenCalledWith({ email: '...' });
    });
});

if you inject repository as:

@InjectRepository(User)
private readonly userRepository!: Repository<User>;

then this should work:

Container.set(ConnectionManager, {
    has: (connectionName: string) => true,
    get: (connectionName: string) => ({
        getRepository: (entityType: any) => {
            switch (entityType) {
                case User:
                    return mockUserRepository;
                default:
                    console.warn(`No mock repository found for ${entityType}`);
            }
        },
    }),
});

Originally posted by @kajkal in https://github.com/typeorm/typeorm-typedi-extensions/issues/33#issuecomment-623199794

A better way of mocking is clearly needed. It seems that the selected solution above does not fully solve the issue since you need to also mock the entire ConnectionManager. In other words, with the above solution only, any getConnection() calls would fail as it is not mocked (not to mention any other methods).

Better way means, you only mock what you need to mock. Pretty much like

Container.set('UserRepository', mockUserRepository)

would be ideal.

NoNameProvided commented 3 years ago

I am not actively maintaining this lib so won't implement this, but if someone comes up with a solution we can give it a go.

For other readers: if you don't need a custom repository then you can just use getRepository() from TypeORM, eg:

import { getRepository } from 'typeorm';
import { Service } from 'typedi';
import { User } from 'your-code';

@Service()
export class UserResolver {
    private readonly userRepository = getRepository(User);
}
hardikkheni commented 2 years ago

can someone tell me is this looks good or not?. it works.

// base.repository.ts
import { EntityTarget, Repository as Repo } from 'typeorm';
import { dataSource } from '..';

export const Repository = <Entity>(target: EntityTarget<Entity>) => {
    return class extends Repo<Entity> {
        constructor() {
            super(target, dataSource.createEntityManager());
        }
    };
};
// user.repository.ts
import { Service } from 'typedi';
import { User } from '../entities';
import { Repository } from './base.repository';

@Service()
export class UserRepository extends Repository(User) {
    findByEmailOrFail(email: string) {
        return this.findOneByOrFail({ email });
    }
}

Apart from this i did't have to do anything. and i am not using this package.