Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to test codegen services with jest #1703

Closed
hendrikgaffo opened this issue Aug 25, 2021 · 12 comments
Closed

Unable to test codegen services with jest #1703

hendrikgaffo opened this issue Aug 25, 2021 · 12 comments

Comments

@hendrikgaffo
Copy link

Describe the bug

We are using NX for our monorepo and created a library that is responsible for the generation and exposure of GraphQL services using codegen as suggested here.
Using said services in our components works just fine. However, when it comes to testing (Jest), we get an error saying:
Can't resolve all parameters for GetExampleGQL: (?).

To Reproduce

1. Create a Library with nx using the following codegen.yml and export the generated services

overwrite: true
schema: "http://your-server/graphql"
generates:
  libs/ngx/data-access/src/lib/generated/angular.ts:
    documents: "libs/ngx/data-access/src/lib/operations/**/*.graphql"
    config:
      querySuffix: GQL
      subscriptionSuffix: GQL
      mutationSuffix: GQL
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-apollo-angular"

2. Use the generated services in a Jest test

import { CommonModule } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GetExampleGQL } from '@my-org/ngx-data-access'; // imports the generated service
import { TranslateModule, TranslateStore } from '@ngx-translate/core';

import { DashboardIndexPageComponent } from './dashboard-index-page.component';

describe('DashboardIndexPageComponent', () => {
  let component: DashboardIndexPageComponent;
  let fixture: ComponentFixture<DashboardIndexPageComponent>;

  let getExample: GetExampleGQL;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [DashboardIndexPageComponent],
      imports: [CommonModule, TranslateModule.forChild()],
      providers: [TranslateStore, GetExampleGQL],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(DashboardIndexPageComponent);
    component = fixture.componentInstance;
    getExample = TestBed.inject(GetExampleGQL);

    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

3. Run tests

yarn nx test my-project --detect-open-handles

Outputs:

 FAIL   my-project  apps/my-project/src/app/features/dashboard/pages/index/dashboard-index-page.component.spec.ts
  ● DashboardIndexPageComponent › should create

    Can't resolve all parameters for GetExampleGQL: (?).

      at syntaxError (../../../packages/compiler/src/util.ts:108:17)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getDependenciesMetadata (../../../packages/compiler/src/metadata_resolver.ts:1010:27)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getTypeMetadata (../../../packages/compiler/src/metadata_resolver.ts:889:20)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getInjectableTypeMetadata (../../../packages/compiler/src/metadata_resolver.ts:1144:17)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver.getProviderMetadata (../../../packages/compiler/src/metadata_resolver.ts:1155:16)
      at ../../../packages/compiler/src/metadata_resolver.ts:1076:38
          at Array.forEach (<anonymous>)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver._getProvidersMetadata (../../../packages/compiler/src/metadata_resolver.ts:1032:15)
      at CompileMetadataResolver.Object.<anonymous>.CompileMetadataResolver.getNgModuleMetadata (../../../packages/compiler/src/metadata_resolver.ts:697:30)
      at JitCompiler.Object.<anonymous>.JitCompiler._loadModules (../../../packages/compiler/src/jit/compiler.ts:129:49)
      at JitCompiler.Object.<anonymous>.JitCompiler._compileModuleAndAllComponents (../../../packages/compiler/src/jit/compiler.ts:117:32)
      at JitCompiler.Object.<anonymous>.JitCompiler.compileModuleAndAllComponentsAsync (../../../packages/compiler/src/jit/compiler.ts:69:33)
      at CompilerImpl.Object.<anonymous>.CompilerImpl.compileModuleAndAllComponentsAsync (../../../packages/platform-browser-dynamic/src/compiler_factory.ts:69:27)
      at TestingCompilerImpl.Object.<anonymous>.TestingCompilerImpl.compileModuleAndAllComponentsAsync (../../../packages/platform-browser-dynamic/testing/src/compiler_factory.ts:59:27)
      at TestBedViewEngine.Object.<anonymous>.TestBedViewEngine.compileComponents (../../../packages/core/testing/src/test_bed.ts:405:27)
      at Function.Object.<anonymous>.TestBedViewEngine.compileComponents (../../../packages/core/testing/src/test_bed.ts:170:25)
      at src/app/features/dashboard/pages/index/dashboard-index-page.component.spec.ts:19:8
      at ../../node_modules/tslib/tslib.js:117:75
      at new ZoneAwarePromise (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:1347:33)
      at Object.__awaiter (../../node_modules/tslib/tslib.js:113:16)
      at src/app/features/dashboard/pages/index/dashboard-index-page.component.spec.ts:14:25
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:407:30)
      at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3765:43)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:406:56)
      at Zone.Object.<anonymous>.Zone.run (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:167:47)
      at Object.wrappedFunc (../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:4250:34)

Expected behavior

The test should pass.

Environment:

├── @angular-devkit/[email protected]
├── @angular/[email protected] 
├── @angular/[email protected] 
├── @apollo/[email protected] 
├── @datorama/[email protected]
├── [email protected] 
├── [email protected] 
└── [email protected] 

It would be nice if someone could share if I am doing something wrong in the test.

@pafflique
Copy link

I think we have similar issue after upgrading from angular 11 to 12.
For each such generated service

  @Injectable({
    providedIn: 'root'
  })
  export class MyGQL extends Apollo.Mutation<MyMutation, MyMutationVariables> {
    document = MyDocument;

    constructor(apollo: Apollo.Apollo) {
      super(apollo);
    }
  }

I get this error while testing with jest:

This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
    This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
    Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.

Shouldn't the ApolloBase be @Injectable ?
Test works fine if I comment out the service.

@spali
Copy link

spali commented Sep 14, 2021

I have the same as @pafflique with generated code, it started after upgrading to jest 27.
regarding the error message and angular doc's, ApolloBase should have the @Injectable decorator anyway.

But I could also workaround by changing the generated code to not use namespace import:

import * as Apollo from 'apollo-angular';

to

import { Apollo as ApolloClient, Query as ApolloQuery, Mutation as ApolloMutation) from 'apollo-angular';

and changing the respective references too.

I started with this test, because I found some jest-preset-angular issues related to namespace imports.
I'm still now sure how and if this is related to the missing @Injectable decorator and if both can fix the problem.

@spali
Copy link

spali commented Sep 14, 2021

just did a test.
putting

Injectable()(ApolloBase);

in test-setup.ts (jest setup file) of the app to test, it works without changing the generated code.
So it seems, that the decorator would also fix the problem.

@WolfSoko
Copy link

Injectable()(ApolloBase); didn't help for me.

Here are some related issues:

@spali
Copy link

spali commented Sep 27, 2021

@WolfSoko

Injectable()(ApolloBase); didn't help for me.

I found too my workaround was instable. But maybe I found it which could also fix your problem.
In my case I had to import ApolloBase and Apollo at in the test-setup.ts to be sure it worked.
Found this out, as I wanted to use the same workaround in a lib, which did not import Apollo.

So my current workaround is:

/**
 * TODO: workaround for https://github.com/kamilkisiela/apollo-angular/issues/1703
 *
 * has to be called in `test-setup.ts`:
 * 
 * ```
 * import { Apollo, ApolloBase } from 'apollo-angular';
 * import { fixApolloBase } from './workaround';
 * fixApolloBase(Apollo, ApolloBase);
 * 
 * ```
 *
 * @param _apollo `Apollo` has to be imported by the caller (forces the called to do so)
 * @param apolloBase `ApolloBase` has to be imported by the caller (forces the called to do so)
 * @returns
 */
export function fixApolloBase(
  _apollo: typeof Apollo,
  apolloBase: typeof ApolloBase
) {
  return Injectable()(apolloBase);
}

Then import and execute the function from within test-setup.ts. The arguments forces the caller (test-setup.ts) in this case to import the required classes:

import { Apollo, ApolloBase } from 'apollo-angular';
import { fixApolloBase } from './workaround';
fixApolloBase(Apollo, ApolloBase);

At least in my case this helped in multiple libs.

@Nielsb85
Copy link

@spali How does your jest.config look like. Above workaround does not work for me right now.

@spali
Copy link

spali commented Sep 29, 2021

@Nielsb85 I use an NX workspace with default generated jest.config.

the workspace jest.config.js:

const { getJestProjects } = require('@nrwl/jest');

module.exports = { projects: getJestProjects() };

the workspace jest.preset.js:

const nxPreset = require('@nrwl/jest/preset');

module.exports = { ...nxPreset };

the library jest.config:

module.exports = {
  displayName: 'graphql-client',
  preset: '../../jest.preset.js',
  setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
  globals: {
    'ts-jest': {
      tsconfig: '<rootDir>/tsconfig.spec.json',
      stringifyContentPathRegex: '\\.(html|svg)$',
    },
  },
  coverageDirectory: '../../coverage/libs/graphql-client',
  transform: {
    '^.+\\.(ts|js|html)$': 'jest-preset-angular',
  },
  snapshotSerializers: [
    'jest-preset-angular/build/serializers/no-ng-attributes',
    'jest-preset-angular/build/serializers/ng-snapshot',
    'jest-preset-angular/build/serializers/html-comment',
  ],
};

@spali
Copy link

spali commented Sep 29, 2021

@Nielsb85 I forget to mention.... be sure to clear jest cache before running tests and if it's a NX workspace to skip the nx-cache --skip-nx-cache. This made me some headages ... due inconsistent results.
Just be sure to always clear the cache before every test run and always skip the nx-cache during testing this workaround.

@Nielsb85
Copy link

@spali Thanks. Cache was already cleared. No luck so far.

@pafflique
Copy link

pafflique commented Oct 19, 2021

I used a "direct flavor" of the workaround.

import { Injectable } from '@angular/core';
import { ApolloBase } from 'apollo-angular';
Injectable()(ApolloBase);

@Maximaximum
Copy link

It has been very difficult to trace this issue to its actual cause, but this is the actual bug: thymikee/jest-preset-angular#963

The issue appears because jest-preset-angular does not support namespace imports like import * as Apollo from 'apollo-angular', and graphql-codegen puts such lines into its generated files.

@kamilkisiela
Copy link
Owner

thanks Maximaximum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants