Testing - Can't resolve all parameters for (ClassName)

27,977

Solution 1

It's because the Http service can't be resolved from the HttpModule, in a test environment. It is dependent on the platform browser. You shouldn't even be trying to to make XHR calls anyway during the tests.

For this reason, Angular provides a MockBackend for the Http service to use. We use this mock backend to subscribe to connections in our tests, and we can mock the response when each connection is made.

Here is a short complete example you can work off of

import { Injectable } from '@angular/core';
import { async, inject, TestBed } from '@angular/core/testing';
import { MockBackend, MockConnection } from '@angular/http/testing';
import {
  Http, HttpModule, XHRBackend, ResponseOptions,
  Response, BaseRequestOptions
} from '@angular/http';

@Injectable()
class SomeService {
  constructor(private _http: Http) {}

  getSomething(url) {
	return this._http.get(url).map(res => res.text());
  }
}

describe('service: SomeService', () => {
  beforeEach(() => {
	TestBed.configureTestingModule({
	  providers: [
		{
		  provide: Http, useFactory: (backend, options) => {
			return new Http(backend, options);
		  },
		  deps: [MockBackend, BaseRequestOptions]
		},
		MockBackend,
		BaseRequestOptions,
		SomeService
	  ]
	});
  });

  it('should get value',
	async(inject([SomeService, MockBackend],
				 (service: SomeService, backend: MockBackend) => {

	backend.connections.subscribe((conn: MockConnection) => {
	  const options: ResponseOptions = new ResponseOptions({body: 'hello'});
	  conn.mockRespond(new Response(options));
	});

	service.getSomething('http://dummy.com').subscribe(res => {
	  console.log('subcription called');
	  expect(res).toEqual('hello');
	});
  })));
});

Solution 2

Using Jest?

In case anyone gets here AND you're using Jest to test your Angular app (hopefully we're a growing minority), you will run into this error if you are not emitting decorators ("emitDecoratorMetadata":true). You'll need to update your tsconfig.spec.json file so it looks like:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "outDir": "../../out-tsc/spec",
    "types": [
      "jest",
      "node"
    ]
  },
  "files": [
  ],
  "include": [
    "**/*.spec.ts",
    "**/*.d.ts"
  ]
}

Solution 3

The issue wasn't really solved in the chosen answer, which is really just a recommendation for writing tests, but rather in the comments, and you have to follow a link and search for it there. Since I had another issue with the same error, I'll add both solutions here.

  1. Solution to the OP's issue:

If you have a barrel (index.ts or multi export file) like this:

export * from 'my.component' // using my.service via DI
export * from 'my.service'

Then you could get an error like EXCEPTION: Can't resolve all parameters for MyComponent: (?).

To fix it, you have to export the service before the component:

export * from 'my.service'
export * from 'my.component' // using my.service via DI
  1. Solution to my issue:

The same error can happen due to a circular dependency which causes an undefined service import. To check, console.log(YourService) after importing it (in your test file - where the issue is happening). If it's undefined, you may have made an index.ts file (barrel) exporting both the service and the file using it (component/effect/whatever you're testing) - by importing the service from the index file where both are exported (making it full circle).

Find that file and import the service you need directly from your.service.ts file instead of the index.

Solution 4

[JEST and ANGULAR]

Also, the problem may occur when you use an external module and you do not import it but use it on your service.

Ex:

import { TestBed } from '@angular/core/testing';
import <ALL needed> from '@ngx-translate/core';

import { SettingsService } from '../../../app/core/services/settings/settings.service';


describe('SettingsService', () => {
  let service: SettingsService;

  beforeAll(() => {
    TestBed.configureTestingModule({
      providers: [
        SettingsService,
        // <All needed>
      ]
    });
    service = TestBed.inject<SettingsService>(SettingsService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

});

Errors will get you nowhere ... But, if you do that this way:

import { TestBed } from '@angular/core/testing';

import { TranslateModule } from '@ngx-translate/core';

import { SettingsService } from '../../../app/core/services/settings/settings.service';


describe('SettingsService', () => {
  let service: SettingsService;

  beforeAll(() => {
    TestBed.configureTestingModule({
      imports: [TranslateModule.forRoot()], // <---
      providers: [
        SettingsService
      ]
    });
    service = TestBed.inject<SettingsService>(SettingsService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

});

Problem disappears.

Solution 5

[JEST and ANGULAR]

In my case, the root cause is the circular dependency, but not the "import service from index" situation. And ng build <project> --prod didn't find the "circular dependency".

Solution:

In the service/component, injecting Injector and injector.get(Service) instead.

Share:
27,977

Related videos on Youtube

Supamiu
Author by

Supamiu

Full stack Developper, mainly focused on Angular2 at the moment. Creator of http://ffxivteamcraft.com/ (https://github.com/ffxiv-teamcraft)

Updated on July 09, 2022

Comments

  • Supamiu
    Supamiu almost 2 years

    Context

    I created an ApiService class to be able to handle our custom API queries, while using our own serializer + other features.

    ApiService's constructor signature is:

    constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);
    
    • MetaManager is an injectable service that handles api's metadatas.
    • ApiConnectorService is a service which is wrapping Http to add our custom headers and signature system.
    • EventDispatcher is basically Symfony's event dispatcher system, in typescript.

    Problem

    When I test the ApiService, I do an initialization in beforeEach:

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports  : [
                HttpModule
            ],
            providers: [
                ApiConnectorService,
                ApiService,
                MetaManager,
                EventDispatcher,
                OFF_LOGGER_PROVIDERS
            ]
        });
    }));
    

    and it works fine.

    Then I add my second spec file, which is for ApiConnectorService, with this beforeEach:

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports  : [HttpModule],
            providers: [
                ApiConnectorService,
                OFF_LOGGER_PROVIDERS,
                AuthenticationManager,
                EventDispatcher
            ]
        });
    }));
    

    And all the tests fail with this error:

    Error: Can't resolve all parameters for ApiService: (MetaManager, ?, EventDispatcher).

    • If I remove api-connector-service.spec.ts (ApiConnectorService's spec file) from my loaded tests, ApiService's tests will succeed.
    • If I remove api-service.spec.ts (ApiService's spec file) from my loaded tests, ApiConnectorService's tests will succeed.

    Why do I have this error? It seems like the context between my two files are in conflict and I don't know why and how to fix this.

  • Günter Zöchbauer
    Günter Zöchbauer almost 8 years
    Why don't you just use MockBackend, BaseRequestOptions instead of { provide: MockBackend, useClass: MockBackend }, { provide: BaseRequestOptions, useClass: BaseRequestOptions },. Aren't these two already provided by HttpModule (I'm pretty sure they are). Also providers: [ { provide: Http, useFactory: (backend, options) => { return new Http(backend, options); }, deps: [MockBackend, BaseRequestOptions] }, can be shortened to {provide: XHRBackend: useExisting: MockBackend}
  • Günter Zöchbauer
    Günter Zöchbauer almost 8 years
    So providers: [{provide: XHRBackend: useExisting: MockBackend}, SomeService] should give you the same result.
  • Paul Samsotha
    Paul Samsotha almost 8 years
    @GünterZöchbauer Yeah you're right. I previously was using XHRBackend as the token, but I forgot I was injecting MockBackend type. So I changed it to MockBackend token. Wasn't thinking about style at the time, it was just a quick fix at the time I was testing.
  • Günter Zöchbauer
    Günter Zöchbauer almost 8 years
    As far as I remember the docs at Angular.io also show this long way. Your answer is fine. Just wanted to point it out. I have seen many examples where people use way too complicated configuration and then complain DI is cumbersome ;-)
  • Supamiu
    Supamiu almost 8 years
    So why do I get ApiConnectorService parameter not resolved? and why only when I start both tests?
  • Paul Samsotha
    Paul Samsotha almost 8 years
    Maybe because you trying to inject the apiservice and not the connector service, and error tells you what missing from the service you are trying to inject.
  • Supamiu
    Supamiu almost 8 years
    Check the example ;) it's what I'm injecting. I changed HttpModule for a MockBackend and tried this way, still having the same error.
  • Paul Samsotha
    Paul Samsotha almost 8 years
    Can you post a complete example. Enough to reproduce the problem. Something like I did above.
  • Supamiu
    Supamiu almost 8 years
    I tried to create a complete example using the quickstart template, seems like I can't. The issue must be from my code but it's really too heavy to paste it here. Since the providers are all good and I provide a good Http now (with MockBackend) I don't see where this can come from...
  • Paul Samsotha
    Paul Samsotha almost 8 years
    What I would is just add stripped down versions of the classes with only the constructor and a single method to test. The problem is the injection, which can be reproduced without anything else in the classes. If you get it to work with the stripped down versions, start adding things until it breaks. Then you know the problem.
  • Paul Samsotha
    Paul Samsotha almost 8 years
    You can work off of this gist.github.com/psamsotha/2cce26fe4c9a3e73b8c8cbaa0fd738be, which works fine
  • Paul Samsotha
    Paul Samsotha almost 8 years
    I just came across this github.com/angular/angular.io/issues/… and thought about you. A shot in the dark, but could this be a possible issue?
  • Supamiu
    Supamiu almost 8 years
    @peeskillet this is exactly the problem I had, I just came here to say it and I saw your comment... :D
  • Avinash Raj
    Avinash Raj over 7 years
    My service constructor looks like constructor(private http: Http, baseName: string) { , so where I need to modify in the above code?
  • Stephen
    Stephen almost 4 years
    Thank you. Nothing in the error message to help figure this out. The key is "emitDecoratorMetadata": true