I am new to nestjs and to jest as well. I am trying to override a guard, but it's not working. So my folder structure is: "src/app.module.ts", "src/modules/auth/guards/myfile-guard.ts", "src/modules/auth/auth.module.ts","src/modules/user/user.controller.ts" and "src/modules/user" contains all user related files. I am testing e2e test for user controller, which have a get route "/users", which requires user to login, and it is determined by a guard and then mentioned of this guard is done in "src/modules/auth/auth.module.ts" as below:
@Module({
imports: [],
controllers: [AuthController],
providers: [
{
provide: APP_GUARD,
useClass: IsAuthenticatedGuard,
},
AuthService,
],
exports: [AuthService],
})
export class AuthModule {}
my guard file: "src/modules/auth/guards/myfile-guard.ts" looks like:
@Injectable()
export class MyGuard extends AuthGuard('local')
implements CanActivate {
constructor(private readonly reflector: Reflector) {
super();
}
public canActivate(context: ExecutionContext): boolean {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler(),
);
if (isPublic) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
const isAuthenticated = request.isAuthenticated();
if (!isAuthenticated) {
throw new UnauthorizedException();
}
return true;
}
}
"src/app.module.ts" file, I have below code:
@Module({
imports: [
AuthModule,
UserModule,
],
controllers: [AppController],
providers: [],
})
export class AppModule {}
in "src/modules/user/user.controller.ts" file, I have below code:
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async getUsers(): Promise<User[]> {
return this.userService.findUsers();
}
}
in "src/test/user-e2e-spec.ts" file, I have below code:
import { MyGuard } from '../src/modules/auth/guards/myfile-guard';
import { AppModule } from '../src/app.module';
describe('UserController (e2e)', () => {
let app: INestApplication;
const canActivate = jest.fn(() => true);
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers: [],
})
.overrideGuard(MyGuard)
.useValue({ canActivate })
.compile();
app = moduleFixture.createNestApplication();
jest.resetAllMocks();
await app.init();
});
describe('@Get()', () => {
const requestUrl = '/users';
canActivate.mockReturnValueOnce(true);
it('returns list of users', async () => {
return request(app.getHttpServer())
.get(requestUrl)
.expect(200);
});
});
});
When I run the test, in place of 200, I always get 401 unauthorized, which means, "MyGuard" is not getting overridden by {canActivate}, I did even console.log in MyGuard class, it always goes there, where as it should not, I tried to search different questions related to same, few of them helped me to reach at this stage as well, below are few links which I already visited and which helped me: Nestjs unit-test - mock method guard Controller integration testing with nestJS using different guards
but still I cannot get it working as per the need, which is returning true from mocked or overridden canActivate. I am sure, either I am doing something wrong or I am missing something small. Please feel free to let me know if any further information is needed from my side I am thanking all in advance for your help
After digging into the core and testing modules of nest, i have found the answer to make it work "the clean way".
You need to leverage useExisting
in your module to alias your guard as a provider:
@Module({
providers: [
FooGuard, // ← this is useless bait for `.overrideProvider`
{
provide: APP_GUARD,
useExisting: FooGuard, // ← this tells to use ↑ as value. will already be overridden at the time of resolving it
}
]
})
class FooModule {}
In your test file now you can use .overrideProvider
. It will override the bait provider, and when you'll be starting your app, the useExisting
reference will be resolved, pointing at your mock guard.
This works because:
In the testing module builder, after calling .compile()
the testing module will call this.applyOverloadsMap
which is a loop over all your overrides. For each, it calls this.container.replace
. Container is roughly going to iterate over each module loaded, and for each will call module.replace(toReplace, options)
.
Now, one small thing about toReplace
is that it is supposed to be the provide
key, but when it comes to global enhancers (APP_GUARD
etc) it is resolved unique so it is not usable.
Knowing that, it is clear that we cannot use APP_GUARD
as a token ref because it will never match... but the question remains: can we target our guard nonetheless?
The doc mentions that useExisting
will reuse the same instance, so the strategy here is to:
FooGuard
). This will declare a provider which we can overrideuseExisting
when declaring the APP_GUARD
with the reference to FooGuard
The resolution of what is FooGuard
happens at .compile()
time, so before the app starts, so the app guard will not be installed as global guard yet when it will be already replaced.
Et voila.
Working example: https://stackblitz.com/edit/nestjs-typescript-starter-cfgwva?file=README.md
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With