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

question: how to mock InjectRepository? #33

Closed
feimosi opened this issue Dec 3, 2018 · 7 comments
Closed

question: how to mock InjectRepository? #33

feimosi opened this issue Dec 3, 2018 · 7 comments
Labels
type: question Questions about the usage of the library.

Comments

@feimosi
Copy link

feimosi commented Dec 3, 2018

I couldn't find any way to mock repositories injected by InjectRepository.
Apparently Container.set is not the way to go.

I ended up mocking the whole library for now

jest.mock('typeorm-typedi-extensions', () => ({
  InjectRepository: () => () => {},
}));

...

service = Container.get(MyService);
service.repository = {...}
@adhityan
Copy link

adhityan commented May 22, 2019

This is what I ended up doing and it works well for most test cases -

I created a mock repository -

export class MockRepository {
    static singletonList: MockRepository[] = [];

    static getInstance(repository?: Function | string) {
        let repositoryString: string;
        if (!repository) repositoryString = 'default';
        else if (typeof repository === 'string') repositoryString = repository;
        else repositoryString = repository.name;

        if (!MockRepository.singletonList[repositoryString])
            MockRepository.singletonList[repositoryString] = new MockRepository();
        return MockRepository.singletonList[repositoryString];
    }

    public find: Function;

    public findOne: Function;

    public save: Function;

    private constructor() {
        this.find = jest.fn();
        this.findOne = jest.fn();
        this.save = jest.fn();
    }
}

and use it in my MockUtils -

import * as typeOrm from 'typeorm';
import Container from 'typedi';
import { MockRepository } from './mocks/repository.mock';
import { MockIoRedis } from './mocks/ioredis.mock';

export class MockUtils {
    static mockOrm() {
        Object.defineProperty(typeOrm, 'getRepository', {
            value: (x: Function) => {
                return MockRepository.getInstance(x.name);
            },
        });

        jest.mock('typeorm-typedi-extensions', () => ({
            InjectRepository: (entityTypeOrConnectionName?: Function | string) => (
                object: object,
                propertyName: string,
                index?: number,
            ) => {
                Container.registerHandler({
                    index,
                    object,
                    propertyName,
                    value: () => MockRepository.getInstance(entityTypeOrConnectionName),
                });
            },
        }));
    }

    static mockRedis() {
        jest.mock('ioredis', () => MockIoRedis);
    }
}

Just stick MockUtils.mockOrm(); at the top of any spec file and you are good to go.

@pbn4
Copy link

pbn4 commented Feb 12, 2020

Is there an easier solution than presented above?

@dir01
Copy link

dir01 commented Feb 12, 2020

@pbn4 not sure what is your use case, but when I first discovered this issue, what I was doing is I was extending typeorm repository with my application-specific methods.

What I am doing now instead is I create a separate class, annotate it as a Service, and inject typeorm repository inside of it. From this point on, if I want to test this application-specific repository itself, or an application as a whole without mocking the database, I spin up a database with testcontainers and configure relevant part of the application with appropriate credentials, or, if I want to test a service separately (more relevant to the discussion), I just Container.set(MyRepository, {getUser: jest.fn(), saveUser: jest.fn()})

@Zardddddd60
Copy link

After reading the source code of typedi and typeorm-typedi-extensions, i find a good way to mock InjectRepository .

the resolver class likes look that, we need to focus on the property name injected by @InjectRepository, userRepository in this example.

// resolver.ts
@Service()
export class RegisterResolver {
    // another service
    @Inject('REGISTER_SERVICE')
    private readonly registerService!: RegisterService;
    // the orm repository
    @InjectRepository(User)
    private readonly userRepository!: Repository<User>;
}

the test file looks like:

import Container from 'typedi';
import { RegisterResolver } from '@root/modules/user/register/register.resolver';

describe('test register resolver', () => {
    test('test', async () => {
        // mock the orm operations used in resolver;
        const saveMock = jest.fn();
        const createMock = jest.fn().mockImplementation(input => ({
            ...input,
            id: 1,
        }));

        // mock the register service;
        sendEmail = jest.fn();
        getUrl = jest.fn();

        // replace actual service
        Container.set('REGISTER_SERVICE', {
            sendEmail,
            getUrl,
        });

        // find the handler using propertyName and replace it with apis you use in your resolver;
        const handler = Container.handlers.find(handler => handler.propertyName === 'userRepository');
        handler!.value = function () {
            return {
                create: saveMock,
                save: createMock,
            };
        }

        // use container to get resolver instance;
        const resolver = Container.get(RegisterResolver);
        // methods may use orm operations;
        await resolver.register({
            password: '123',
            firstName: 'l',
            lastName: 'dd',
            email: 'ldd@163.com',
        });

        // mocked orm operation get called.
        expect(saveMock).toHaveBeenCalled();
        expect(createMock).toHaveBeenCalled();
    });
});

this is a brief example and it satisfied my needs.

@kajkal
Copy link

kajkal commented May 3, 2020

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}`);
            }
        },
    }),
});

@NoNameProvided NoNameProvided added the type: question Questions about the usage of the library. label Jan 15, 2021
@NoNameProvided
Copy link
Member

Closing as solved, as multiple solutions have been posted.

@NoNameProvided NoNameProvided changed the title How to mock InjectRepository question: how to mock InjectRepository? Jan 15, 2021
@github-actions
Copy link

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

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: question Questions about the usage of the library.
Development

No branches or pull requests

7 participants