suites-dev / suites

A meta-framework that focuses on helping developers build solid test suites, eliminates boilerplate code, and improves their unit testing process
https://suites.dev
Apache License 2.0
355 stars 11 forks source link

Suits can not locate the injected module when using Jest and Inversify #457

Closed ceoworks closed 3 days ago

ceoworks commented 1 week ago

Is there an existing issue for this?

Current behavior

I'm using suits with inversify and jest. I reproduced the class hierarchy from the docs and while it works fine with Inversify, it seems to not locating the injected services when using both inversify and jest:

Suites Warning: Unreachable Exposed Dependency Detected.
The dependency 'UserApi' has been exposed but cannot be reached within the current testing context.
This typically occurs because 'UserApi' is not a direct dependency of the unit under test (UserService) nor any
of its other exposed dependencies. Exposing 'UserApi' without it being accessible from the unit under test or
its dependencies may lead to incorrect test configurations. To resolve this, please review and adjust your testing
setup to ensure all necessary dependencies are interconnected. Alternatively, if 'UserApi' does not influence
the unit under test, consider removing its exposure from your test setup.
For detailed instructions and best practices, refer to our documentation: https://suites.dev/docs.

Please check Readme for more details.

Minimum reproduction code

https://github.com/ceoworks/suits-inversify-bug

Steps to reproduce

  1. yarn install
  2. yarn jest

Expected behavior

I should not have the error.

Suites version

3.0.0

Node.js version

20.16.0

In which operating systems have you tested?

Other

No response

omermorad commented 6 days ago

Hello @ceoworks

In that scenario, you should use the string-based injection token, which for you is the enum (DI), instead of using the class as the identifier (look here)

const { unit, unitRef } = await TestBed.sociable(UserService).expose<UserApi>(DI.UserApi).compile(); 

Having said that, I've now noticed a bug with the identifier in the expose method. I'll be opening a new issue for this. In the meantime, please use this workaround:

const { unit, unitRef } = await TestBed.sociable(UserService).expose<UserApi>(DI.UserApi as never).compile();

Please let me know if it worked for you :) CC: @qballer

ceoworks commented 5 days ago

Hi @omermorad, after reading more docs and trying your fixes, it seems like I still have the issues.

I changed the code to the following and got the following error:

Code with DI enum usage and UserApi ref get ```typescript describe('sociable', () => { let underTest: UserService; let userApi: Mocked; let database: Mocked; let httpService: Mocked; beforeAll(async () => { const {unit, unitRef} = await TestBed.sociable(UserService).expose(DI.UserApi as never).compile(); underTest = unit; userApi = unitRef.get(DI.UserApi); database = unitRef.get(DI.Database); httpService = unitRef.get(DI.HttpService); }); it('should work', async () => { const userFixture: User = { id: 1, name: 'John' }; // Mock the HttpService dependency httpService.get.mockResolvedValue({ data: userFixture }); database.saveUser.mockResolvedValue(userFixture.id); const result = await underTest.generateRandomUser(); expect(httpService.get).toHaveBeenCalledWith('/random-user/'); expect(database.saveUser).toHaveBeenCalledWith(userFixture); }); }); ``` Error: ``` ❯ yarn jest yarn run v1.22.17 $ /Users/stasjs/projects/suites-inversify/suits-inversify-bug/node_modules/.bin/jest Suites Warning: Unreachable Exposed Dependency Detected. The dependency 'undefined' has been exposed but cannot be reached within the current testing context. This typically occurs because 'undefined' is not a direct dependency of the unit under test (UserService) nor any of its other exposed dependencies. Exposing 'undefined' without it being accessible from the unit under test or its dependencies may lead to incorrect test configurations. To resolve this, please review and adjust your testing setup to ensure all necessary dependencies are interconnected. Alternatively, if 'undefined' does not influence the unit under test, consider removing its exposure from your test setup. For detailed instructions and best practices, refer to our documentation: https://suites.dev/docs. FAIL ./index.spec.ts sociable ✕ should work (1 ms) ● sociable › should work DependencyResolutionError: Dependency resolution error (code ER040) The dependency identified by ''HttpService'' could not be located within the current testing context. This error usually occurs when attempting to access a dependency that has not been mocked or exposed during the test setup. Please verify that ''HttpService'' is correctly mocked or explicitly exposed in the TestBed configuration if it is essential for your tests. Review your testing setup to ensure all required dependencies are correctly included and configured. For detailed setup instructions and troubleshooting, please visit: https://suites.dev/docs. 73 | userApi = unitRef.get(DI.UserApi); 74 | database = unitRef.get(DI.Database); > 75 | httpService = unitRef.get(DI.HttpService); | ^ 76 | }); 77 | 78 | it('should work', async () => { at UnitReference.get (node_modules/@suites/core.unit/dist/services/unit-reference.js:37:19) at index.spec.ts:75:27 at fulfilled (index.spec.ts:17:58) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 0 | 0 | 0 | 0 | ----------|---------|----------|---------|---------|------------------- Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 2.358 s, estimated 3 s Ran all test suites. error Command failed with exit code 1. ```

My goal is to be able to get HttpService and I still can not 😞 I updated the code in the repository so you can easily reproduce it 🙏

qballer commented 5 days ago

Please refer to this section in the docs

You cannot retrieve an exposed class from the unitRef using .get() after calling .expose(). 
This limitation is by design. The purpose of .expose() is to make the class a real, non-mocked 
dependency within the test context,  making it part of the "system under test."

In the example given above, this is what you are doing AFAICT.

omermorad commented 3 days ago

Closing this one @ceoworks

ceoworks commented 2 days ago

@qballer @omermorad you are correct, I was trying to get the exposed module, which was leading to an error. However, after I updated the code to not getting the exposed ref, it still gives me an error:

Hi @omermorad, after reading more docs and trying your fixes, it seems like I still have the issues.

I changed the code to the following and got the following error:

Code with DI enum usage and UserApi ref get ```typescript import 'reflect-metadata'; import {injectable, inject} from "inversify"; import {Mocked, TestBed} from "@suites/unit" export interface User { id: number; name: string; } export interface IncomingEvent { type: string; data: unknown; } const enum DI { UserApi = "UserApi", HttpService = "HttpService", Database = "Database", UserService = "UserService" } @injectable() export class HttpService { async get(url: string): Promise { console.log(`request: ${url}`); return; } } @injectable() export class UserApi { constructor( @inject(DI.HttpService) private http: HttpService ) {} async getRandom(): Promise { const response = await this.http.get('/random-user'); return response as User; } } @injectable() export class Database { async saveUser(user: User): Promise { console.log(`user: ${user}`); return 1; } } @injectable() export class UserService { constructor( @inject(DI.UserApi) private userApi: UserApi, @inject(DI.Database) private database: Database) {} async generateRandomUser(): Promise { try { const user = await this.userApi.getRandom(); return this.database.saveUser(user); } catch (error) { return false; } } } describe('sociable', () => { let underTest: UserService; let database: Mocked; let httpService: Mocked; beforeAll(async () => { const {unit, unitRef} = await TestBed.sociable(UserService).expose(DI.UserApi as never).compile(); underTest = unit; database = unitRef.get(DI.Database); httpService = unitRef.get(DI.HttpService); }); it('should work', async () => { const userFixture: User = { id: 1, name: 'John' }; // Mock the HttpService dependency httpService.get.mockResolvedValue({ data: userFixture }); database.saveUser.mockResolvedValue(userFixture.id); const result = await underTest.generateRandomUser(); expect(httpService.get).toHaveBeenCalledWith('/random-user/'); expect(database.saveUser).toHaveBeenCalledWith(userFixture); }); }); ``` Error: ```shell ❯ yarn jest yarn run v1.22.17 $ /Users/stasjs/projects/suits-inversify-bug/node_modules/.bin/jest Suites Warning: Unreachable Exposed Dependency Detected. The dependency 'undefined' has been exposed but cannot be reached within the current testing context. This typically occurs because 'undefined' is not a direct dependency of the unit under test (UserService) nor any of its other exposed dependencies. Exposing 'undefined' without it being accessible from the unit under test or its dependencies may lead to incorrect test configurations. To resolve this, please review and adjust your testing setup to ensure all necessary dependencies are interconnected. Alternatively, if 'undefined' does not influence the unit under test, consider removing its exposure from your test setup. For detailed instructions and best practices, refer to our documentation: https://suites.dev/docs. FAIL ./index.spec.ts sociable ✕ should work (1 ms) ● sociable › should work DependencyResolutionError: Dependency resolution error (code ER040) The dependency identified by ''HttpService'' could not be located within the current testing context. This error usually occurs when attempting to access a dependency that has not been mocked or exposed during the test setup. Please verify that ''HttpService'' is correctly mocked or explicitly exposed in the TestBed configuration if it is essential for your tests. Review your testing setup to ensure all required dependencies are correctly included and configured. For detailed setup instructions and troubleshooting, please visit: https://suites.dev/docs. 71 | underTest = unit; 72 | database = unitRef.get(DI.Database); > 73 | httpService = unitRef.get(DI.HttpService); | ^ 74 | }); 75 | 76 | it('should work', async () => { at UnitReference.get (node_modules/@suites/core.unit/dist/services/unit-reference.js:37:19) at index.spec.ts:73:27 at fulfilled (index.spec.ts:17:58) ```

So I am still not able to get mocked HttpService 😞 I carefully checked the code and I can't find any more the differences between my code and documentation, I kindly ask your help here 🙏