Saturday 26 September 2020

Nest.js Jest cannot mock testing app but can mock testing controller

I have a Jest Nest.js controller test where I can stub the calls to the live database. It works fine.

I would like to have 'stubbed e2e' tests using nests HTTP Server (I am using supertest). However, when I import the AppModule I cannot seem to get jest to override anything.

Here is my working controller setup using stubbed classes.

describe('Working stubbed controller test', () => {
    let controller;

    beforeAll(async () => {
        const moduleFixture = await Test.createTestingModule({
            controllers: [MyController],
            providers: [
                {
                    provide: ObjectInjectionDependency,
                    useClass: ObjectInjectionDependency
                },
                {
                    provide: '@string/injection/dependency',
                    useClass: DbOjectMockIAmUsing
                },
                {
                    provide: '@another/string/injection/dependency',
                    useValue: '@another/string/injection/dependency',
                },
                {
                    provide: DbConnection,
                    useValue: {
                        selectOne: () => null,
                        selectAll: () => null
                    }
                }
            ]
        })
        .compile();

        controller = moduleFixture.get<MyController>(MyController)
    });

    it('/GET roots', async () => {
        const request = {
            method: "GET",
            params: {},
            query: {}
        }

        expect(
            await controller.all(request)
        ).toHaveProperty(
            'data'
        )
    });
});

Here is my unsuccessful attempt at incorporating stubbed classes along with the HTTP server using supertest. The stubbed classes are ignored.

describe('Bypassing stubs application test', () => {
    let app;
    let server;

    beforeAll(async () => {
        const moduleFixture = await Test.createTestingModule({
            imports: [AppModule],
            providers: [
                {
                    provide: ObjectInjectionDependency,
                    useClass: ObjectInjectionDependency
                },
                {
                    provide: '@string/injection/dependency',
                    useClass: DbOjectMockIAmUsing
                },
                {
                    provide: '@another/string/injection/dependency',
                    useValue: '@another/string/injection/dependency',
                },
                {
                    provide: DbConnection,
                    useValue: {
                        selectOne: () => null,
                        selectAll: () => null
                    }
                }
            ]
        })
        .compile();

        app = moduleFixture.createNestApplication();
        server = app.getHttpServer()
        await app.init();
    });

    it('/GET roots', async () => {
        expect(
            await request(server).get('/myEndpoint')
        ).toMatchObject({
            'statusCode': 200
        })
    });
});

I tried using the overrideProvider() methods but they didn't work either

const moduleFixture = await Test.createTestingModule({
    imports: [AppModule]
})
.overrideProvider(ObjectInjectionDependency)
    .useClass(ObjectInjectionDependency)
.overrideProvider('@string/injection/dependency')
    .useClass(DbOjectMockIAmUsing)
.overrideProvider('@another/string/injection/dependency')
    .useValue('@another/string/injection/dependency')
.overrideProvider(DbConnection)
    .useValue({
        selectOne: () => null,
        selectAll: () => null
    })
.compile() 

I also tried using Jest to override the classes

Jest.mock('@path/to/dbconnection', () => {
    selectOne: () => null,
    selectAll: () => null
}))

All didn't seem to have any effect.

I tried spyOn()

jest.spyOn(DbConnection, 'selectOne').mockImplementation(() => null);
jest.spyOn(DbConnection, 'selectAll').mockImplementation(() => null);

but I seem to get a strange error

No overload matches this call.
  Overload 1 of 4, '(object: typeof DbConnection, method: never): SpyInstance<never, never>', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'never'.
  Overload 2 of 4, '(object: typeof DbConnection, method: never): SpyInstance<never, never>', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'never'.ts(2769)

I understand that testing the controller is 'good enough' but I am still curious what I am doing wrong, as I am sure I will find a use case for this testing method in future.

EDIT:

It turns out I was encountering two problems. Firstly, for whatever reason, I struggled to have Jest mock a method of a class. As per @Estus Flask's suggestion, I can now at least mock/stub methods

import { DbConnection } from '@some/path';

jest.spyOn(DbConnection.prototype, 'selectOne').mockReturnValue(null); 

Secondly, nearly all tutorials explaining to mock using the explicit import path

import { DbConnection } from '@some/path';

jest.mock('@some/path');
DbConnection.mockReturnValue(null);

left out the detail about typescripts type checking which I discovered from this answer causing issues.

let myMock = <jest.Mock<DbConnection>>DbConnection;

While the initial error described above was different, the type casting and examples from the linked answer solved a lot of confusion.

Still, given there is a bounty, perhaps someone can explain why the providers array is pretty much ignored when the import array contains the AppModule



from Nest.js Jest cannot mock testing app but can mock testing controller

No comments:

Post a Comment