kamilkisiela / apollo-angular

A fully-featured, production ready caching GraphQL client for Angular and every GraphQL server 🎁
https://apollo-angular.com
MIT License
1.5k stars 310 forks source link

Feedback for “Testing” - Please document how to handle generated GQL classes in Tests #1903

Closed apazureck closed 1 month ago

apazureck commented 1 year ago

Hi,

I am currently struggleing using my generated GQLs for testing and cannot find any solution when searching online.

I have a component, which depends on a service using the generated GQL with Apollo Codegen:

Test:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ApolloTestingModule } from 'apollo-angular/testing';

import { SettingsComponent } from './settings.component';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [SettingsComponent],
      imports: [ApolloTestingModule],
    }).compileComponents();

    fixture = TestBed.createComponent(SettingsComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

Component:

export class SettingsComponent implements OnDestroy, OnInit {
  constructor(
    private settingsService: SettingsService,
    private snackBar: MatSnackBar
  ) {}
  // omitted...

Service:

@Injectable({
  providedIn: 'root',
})
export class SettingsService implements OnDestroy {
  constructor(
    private fullConfigurationQuery: FullConfigurationGQL,
    private changeConfigMutation: ChangeConfigGQL,
    private configChanged: ConfigChangedGQL
  ) {}
// omitted...

FullConfigurationGQL:

@Injectable({
    providedIn: 'root'
  })
  export class FullConfigurationGQL extends Apollo.Query<FullConfigurationQuery, FullConfigurationQueryVariables> {
    override document = FullConfigurationDocument;

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

So when I use the ApolloTestingModule I would expect it has some kind of provider for Apollo. But it does not seem so. This is my test error:

Error: NG0202: 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.

As you can see the service as well as the GQL have those tags, but the Apollo class does not. So how can I provide the mock to be used by the generated GQL class? It would be nice to use the controller in the long term for evaluating the queries.

And I know, I could and definitly should mock the service in the first place (which I will do in the future), but that would only postpone my problem. I would like to use the Apollo Mock there, if possible.

How can I do this?

If I figure something out I'll be happy to share it here and woudl gladly update the documentation.

Cheers!

ghost commented 1 year ago

@apazureck Did you ever figure out a way to fix this?

We've just run into the exact same problem with a newly generated angular project.

vampyr09 commented 1 year ago

It's far from perfect but what will work is to provide the GQL in your TestBed:

import { Apollo } from 'apollo-angular';

TestBed.configureTestingModule({
  providers: [
    {
      provide: FullConfigurationGQL,
      useFactory: (apollo: Apollo) => new FullConfigurationGQL(apollo)
    }
  ]
});

You could write yourself a Wrapper, this will at least remove some of the boilerplate (if you need to provide multiple GQL at the same time)

import { Apollo, Query } from 'apollo-angular';

type ApolloNewable = new (apollo: Apollo) => Query;

function provideGQL(...gqlProviders: ApolloNewable[]) {
  return gqlProviders.map(gql => ({
    provide: gql,
    useFactory: (apollo: Apollo) => new gql(apollo)
  }));
}

TestBed.configureTestingModule({
  providers: [
    ...provideGQL(FullConfigurationGQL),
  ]
});

But in my opinion it is still sad that this might be necessary.

Butterbluemchen commented 11 months ago

I had the same problem. As a workaround I changed the imports in the generated file like this:

import { gql, Apollo as ApolloClient } from 'apollo-angular';
import * as Apollo from 'apollo-angular';

and then for every constructor: constructor(apollo: ApolloClient)

You might create a script which runs after the code generation, to do this for you.

lodeli-lulu commented 5 months ago

I had the same problem too. I'm using Jest as test framework with jest-preset-angular.

I could solve it by changing the include array in the tsconfig.spec.json from:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    // Your compiler options
  },
  "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}

to this:

 {
  "extends": "./tsconfig.json",
  "compilerOptions": {
    // Your compiler options
  },
  "include": ["src/**/*.ts"] // Optionally you can still use `src/**/*.spec.ts` and `src/**/*.d.ts` and add the path to the graphql-codegen generated file.
}