Closed jeanfortheweb closed 5 years ago
Here is the code of my helpers file:
import { getRepositoryToken } from '@nestjs/typeorm';
import { TestingModuleBuilder } from '@nestjs/testing';
import { Permission, PermissionService, PermissionFlags, Identifiable } from '../src/permission';
import { AuthStrategy } from '../src/auth/auth.strategy';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-strategy';
import { User } from '../src/user';
import { Type } from '@nestjs/common';
import { DeepPartial, Repository } from 'typeorm';
import * as faker from 'faker';
import * as supertest from 'supertest';
import { NestApplication } from '@nestjs/core';
faker.seed(2103);
interface Helper<T = any> {
init(builder: TestingModuleBuilder): TestingModuleBuilder;
helpers: { [K in keyof T]: any };
}
export type RepositoryHelper<T extends string, E> = {
[K in T]: {
generate(data?: DeepPartial<E>): E;
} & Repository<E>
};
export function withRepositoryHelper<N extends string, T extends Identifiable>(
name: N,
type: Type<T>,
creator: () => DeepPartial<T>,
): Helper<RepositoryHelper<N, T>> {
let data: T[] = [];
const generate = (data: DeepPartial<T> = {}) => {
return {
...creator(),
...data,
};
};
const repository = {
create: jest.fn((data: DeepPartial<T>) => {
const entity = new type();
for (const [property, value] of Object.entries(data)) {
entity[property as keyof T] = value;
}
return entity;
}),
find: jest.fn().mockImplementation(() => data),
findOne: jest.fn().mockImplementation(() => faker.random.arrayElement(data)),
save: jest.fn(user => user),
};
data = Array(10)
.fill(0)
.map(() => repository.create(creator()));
return {
init(builder: TestingModuleBuilder) {
return builder.overrideProvider(getRepositoryToken(type)).useValue(repository);
},
helpers: {
[name]: {
generate,
...repository,
},
},
};
}
export interface SecurityHelpers {
security: {
setAuthenticated(value: boolean): void;
setGranted(flags: PermissionFlags): void;
};
}
export function withSecurityHelper(user?: User): Helper<SecurityHelpers> {
let authenticated: boolean = false;
let granted: PermissionFlags = PermissionFlags.NONE;
if (user === undefined) {
user = new User();
user.id = 1;
user.createdAt = new Date();
user.useruuid = '';
user.email = 'foo@bar.com';
user.password = 'xxx';
}
const authStrategy = class extends PassportStrategy(
class extends Strategy {
authenticate() {
if (authenticated === false) {
this.fail(400);
} else {
this.success(user);
}
}
},
'jwt',
) {
validate(user: User) {
return user;
}
};
const permissionService = {
granted: jest.fn(
(securityIdentity: any, resourceIdentity: any, permission: PermissionFlags) => {
return (granted & permission) === granted;
},
),
};
return {
init(builder) {
return builder
.overrideProvider(PermissionService)
.useValue(permissionService)
.overrideProvider(AuthStrategy)
.useClass(authStrategy)
.overrideProvider(getRepositoryToken(Permission))
.useValue({});
},
helpers: {
security: {
setAuthenticated(value: boolean) {
authenticated = value;
},
setGranted(flags: PermissionFlags) {
granted = flags;
},
},
},
};
}
interface TestSuite<T> {
init(builder: TestingModuleBuilder): Promise<void>;
request(): supertest.SuperTest<supertest.Test>;
helpers: T;
}
export function createTestSuite<H1>(): TestSuite<{}>;
export function createTestSuite<H1>(helper1: Helper<H1>): TestSuite<H1>;
export function createTestSuite<H1, H2>(
helper1: Helper<H1>,
helper2: Helper<H2>,
): TestSuite<H1 & H2>;
export function createTestSuite<H1, H2, H3>(
helper1: Helper<H1>,
helper2: Helper<H2>,
helper3: Helper<H3>,
): TestSuite<H1 & H2 & H3>;
export function createTestSuite<H1, H2, H3, H4>(
helper1: Helper<H1>,
helper2: Helper<H2>,
helper3: Helper<H3>,
helper4: Helper<H4>,
): TestSuite<H1 & H2 & H3 & H4>;
export function createTestSuite<H1, H2, H3, H4, H5>(
helper1: Helper<H1>,
helper2: Helper<H2>,
helper3: Helper<H3>,
helper4: Helper<H4>,
helper5: Helper<H5>,
): TestSuite<H1 & H2 & H3 & H4 & H5>;
export function createTestSuite<U>(...helpers: Helper[]) {
let app: NestApplication;
return {
async init(builder: TestingModuleBuilder) {
const testingModule = await helpers
.reduce((builder, helper) => helper.init(builder), builder)
.compile();
app = testingModule.createNestApplication();
await app.init();
},
request() {
return supertest(app.getHttpServer());
},
helpers: helpers.reduce(
(helpers, helper) => ({
...helpers,
...helper.helpers,
}),
{},
),
};
}
To clarify the current dependencies:
AuthModule <- TypeOrm PermissionModule <- TypeOrm UserModule <- AuthModule, PermissionModule, TypeOrm
There is actually no circular dependency at all. This is also why the actual app runs just fine.
Circular dependency error is being thrown when type passed to the addProvider
is undefined
. Since your issue appears only if you move your functions from one file to another one, I'd say that either of these: PermissionService
or AuthStrategy
is undefined
. Frequently, barrel files lead to this issue (see here) so avoid them if you use any. Also, imports in your helper file (and its imported file) as well as relations between classes can also affect your code. Basically, you can add console.log
in your init(builder)
to check which one isn't defined in the runtime and afterwards, simply reorganise your code. It's the common TypeScript issue unfortunately.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
I'm submitting a...
Current behavior
When creating the TestingModule(Builder) and the overrides outside of the actual spec file, Nest reports a circular dependency.
BUT: When I move all the code from my helper file to the actual spec file, it's working fine. 100% same code.
The Error:
Expected behavior
No circular dependency (which does not happen when running the actual app) when overriding or creating a TestingModule outside of the spec file.
Minimal reproduction of the problem with instructions
Just put the
overrideProvider
...and such calls in a different file outside of the spec file, likehelpers.ts
What is the motivation / use case for changing the behavior?
Im creating a set of test helpers for common overrides across multiple e2e test spec files. These helpers setup common overrides for me.
Environment