简体   繁体   中英

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 url', 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

For anyone else coming across this question, the problem (at least in my case) is using

imports: [AppModule]

From what I can tell, Nest resolves all dependencies itself when using imports and since the AppModule is the module that basically loads all the app dependencies, it seems that for whatever reason, all the classes I give in the providers array are ignored.

To solve this I just had to use my original method but call the HTTP server like so

const response = await request(server).get('myUrl');

So the final structure was

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

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

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

    it('/GET url', async () => {
        const response = await request(server).get('myUrl');
        
        expect(response.body.data).toBe('stuff');
    });
});

Teardown methods not included

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM