Evan Stern
Evan Stern

Reputation: 157

NestJS Mocking a Mixin that returns a Guard

I'm hoping to get some insight into an issue I'm having.

I have a mixin that produces a guard. The resulting guard uses a service that is injected. Here's the code for the mixin:

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  mixin,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AccountService } from 'src/modules/account/account.service';

export const ForAccountGuard = (
  paramName: string,
  { required = false }: { required?: boolean } = {}
) => {
  @Injectable()
  class _ForAccountGuard implements CanActivate {
    constructor(private readonly accountService: AccountService) {}

    async canActivate(context: ExecutionContext) {
      const ctx = GqlExecutionContext.create(context);
      const accountId = ctx.getArgs()[paramName];
      const currentUser = ctx.getContext().user;

      if (required && !accountId) {
        return false;
      }

      if (accountId) {
        const account = await this.accountService.findUserAccount(
          accountId,
          currentUser.id
        );
        return !!account;
      }

      return true;
    }
  }

  return mixin(_ForAccountGuard);
};

In my tests for a resolver that uses this mixin as a guard I'm doing the following:

  @Query(() => [PropertyEntity])
  @UseGuards(ForAccountGuard('accountId'))
  async allProperties(@Args() { accountId }: AllPropertiesArgs) {
    // <implementation removed>
  }

So, the issue I'm running into is that I get the following error when running tests:

    Cannot find module 'src/modules/account/account.service' from 'modules/common/guards/for-account.guard.ts'

    Require stack:
      modules/common/guards/for-account.guard.ts
      modules/property/property.resolver.spec.ts

It looks like the injected AccountService isn't being resolved.

I'm not exactly sure how to tell Nest's testing module to override a guard that is a mixin. I've been trying it like this, but it doesn't seem to be working:

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PropertyResolver,
        ...
      ],
    })
      .overrideGuard(ForAccountGuard)
      .useValue(createMock<typeof ForAccountGuard>())
      .compile();
    );
  });

So, how am I supposed to mock out a guard that is a mixin?

Upvotes: 4

Views: 2667

Answers (1)

Evan Stern
Evan Stern

Reputation: 157

Okay, after tinkering a bit, I figured out a solution.

Abandoning the overrideGuard method and just going for a straight-up jest.mock of the entire mixin seems to do the trick.

So, I created a mock:

import { CanActivate, Injectable, mixin } from '@nestjs/common';

export const mockForAccountGuard = () => {
  @Injectable()
  class _ForAccountGuardMock implements CanActivate {
    canActivate() {
      return true;
    }
  }

  return mixin(_ForAccountGuardMock);
};

And then I used jest.mock to mock it out:

// in my test file

import { mockForAccountGuard } from '../common/guards/__mocks__/for-account.guard';
import { ForAccountGuard } from '../common/guards/for-account.guard';

jest.mock('../common/guards/for-account.guard', () => ({
  ForAccountGuard: mockForAccountGuard,
}));

...

describe('PropertyResolver', () => {
  ...
  beforeEach(() => {
    ...
    const module: TestingModule = await Test.createTestingModule({
      ...
    }).compile() // note, not using overrideGuards here
  });
})

And that seems to do the trick!

Upvotes: 3

Related Questions