suites-dev/suites

Weird behaviour with type arguments when mocking Drizzle

fermentfan opened this issue · 1 comments

Hi! I don't know if I did something fundamentally wrong or if there is an issue with the mocking of dependencies with type arguments, but I have the following situation:

Dependency to test:

export class LocationService {
  private readonly logger = new Logger(LocationService.name);
  constructor(
    private readonly drizzle: DrizzleService,
  ) {}
//REMOVED OTHER FUNCTIONS FOR READABILITY
}

Dependency to mock:

@Injectable()
export class DrizzleService<TSchema extends Record<string, unknown> = Record<string, never>> extends PgDatabase<PostgresJsQueryResultHKT, TSchema>
{}

export const drizzleService: FactoryProvider = {
  provide: DrizzleService,
  inject: [ConfigService],
  useFactory: async (configService: ConfigService) => {
   // REMOVED FOR READABILITY
  }

I know the type on the DrizzleService looks weird, but Drizzle heavily relies on TypeScript with generics and I wanted to have a simple class for dependency injection.

Now the problem appears when I try to fetch the dependency for use in the test suite:

describe('LocationService', () => {
  let locationService: LocationService;
  let prismaService: PrismaService;
  let drizzleService: DrizzleService;

  beforeAll(() => {
    const { unit, unitRef } = TestBed.create(LocationService).compile();
    locationService = unit

    drizzleService = unitRef.get(DrizzleService);
  })

On assigning the drizzleService variable it throws the following error:

TS2322: Type 'Mocked<DrizzleService<Record<string, unknown>>>' is not assignable to type 'DrizzleService<Record<string, never>>'.   Types of property 'query' are incompatible.     Type '{}' is missing the following properties from type 'DrizzleTypeError<"Seems like the schema generic is missing - did you forget to add it to your DB type?">': $brand, $error

After explicitly typing it and debugging into the constructor I can see that the dependency is just an empty mocked object. Any ideas?

Hello @fermentfan,

You are right about the error. The unitRef method returns the reference for the given dependency, but it is automatically mocked by Automock. Therefore, the returned type from unitRef.get(...) will always be a mocked instance, and it should be typed using the jest.Mocked generic type.

Here's the corrected code snippet:

import Mocked = jest.Mocked;

describe('LocationService', () => {
  let locationService: LocationService;
  let prismaService: Mocked<PrismaService>;
  let drizzleService: Mocked<DrizzleService>;

  beforeAll(() => {
    const { unit, unitRef } = TestBed.create(LocationService).compile();
    locationService = unit;

    drizzleService = unitRef.get(DrizzleService);
  })

By using Mocked<DrizzleService>, TypeScript will recognize that drizzleService is a mocked instance and properly infer its type, resolving the type mismatch issue.

You can find more information on this topic in the Automock documentation:
https://github.com/automock/automock/blob/master/docs/automock.md#dependency-references-and-instance-access

Let me know if it worked for you :)