I'm trying to write an e2e test with supertest where my controller actually makes use of the @Session()
decorator. I however don't want to have the full burden of initiating a session with db connection and so on so my app in the test does not actually initialize the session.
I'd instead like to mock away the data provided by the Decorator in the first place and replace it with static data. I can't really find a solution as to how to realize this however.
Sample from controller:
@Get('/user/me')
public async getMe(@Session() session: Record<string, unknown>) {
if (!session?.user) {
throw new InternalServerErrorException();
}
return session.user;
}
What I'd ideally like the mock to look like:
jest.mock(Session, jest.fn().mockImplementation(() => {
return { user: { name: "test user" } };
})
however this won't work.
According to the official TypeScript documentation Parameter decorators can only be used to observe that a parameter has been set on a particular method. As this is not actually what's happening when using the @Session()
decorator I tried having a look at the source code of how nestjs actually implements these decorators and I'm having at some trouble understanding it correctly.
If I'm not mistaking it seems like the decorator writes some metadata another decorator (probably @Get()
in this case?) can make use of and extract the necessary data based on that.
I'm a little confused as to how to test this properly so I'd be very thankful for some advice :)
===========================================================================
Update: I will now go ahead and instead of mocking the Session decorator itself mock the req.session while setting up my app
in beforeAll()
hook. Therefore I chose the following solution:
app.use((req, res, next) => {
req.session = {
user: {
firstName: 'Max',
lastName: 'Mustermann',
},
};
next();
});
I'll still be very happy in case someone knows a better solution.
To solve the problem create a common function to which creates the nest application for e2e and provided hooks config which enables overriding of testing module and application middleware injection of express
/**
* Hook for overriding the testing module
*/
export type TestingModuleCreatePreHook = (
moduleBuilder: TestingModuleBuilder,
) => TestingModuleBuilder;
/**
* Hook for adding items to nest application
*/
export type TestingAppCreatePreHook = (
app: NestExpressApplication,
) => Promise<void>;
/**
* Sets basic e2e testing module of app
*/
export async function basicE2eSetup(
config: {
moduleBuilderHook?: TestingModuleCreatePreHook;
appInitHook?: TestingAppCreatePreHook;
} = {},
): Promise<[NestExpressApplication, TestingModule]> {
let moduleBuilder: TestingModuleBuilder = Test.createTestingModule({
imports: [AppModule],
});
if (!!config.moduleBuilderHook) {
moduleBuilder = config.moduleBuilderHook(moduleBuilder);
}
const moduleFixture: TestingModule = await moduleBuilder.compile();
const app = moduleFixture.createNestApplication<NestExpressApplication>();
if (config.appInitHook) {
await config.appInitHook(app);
}
return [await app.init(), moduleFixture];
}
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
[app] = await basicE2eSetup({
moduleBuilderHook: (moduleBuilder) => {
// your overrides go here
// Refer: https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
// eg: moduleBuilder.overrideProvider(ProviderName).useValue(value)
return moduleBuilder;
},
appInitHook: async (app) => {
const result = await someAction();
// or get some service from app
// eg: const service = app.get<YourService>(YourService)
app.use((req, res, next) => {
// do something with request of response object
next();
})
}
});
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect((res) => {
expect(res.text).toContain('Hi There');
});
});
});