Testing - Can't resolve all parameters for (ClassName)
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.
- 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
- 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.
Related videos on Youtube
![Supamiu](https://i.stack.imgur.com/r7671.jpg?s=256&g=1)
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, 2022Comments
-
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 wrappingHttp
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 inbeforeEach
: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 thisbeforeEach
: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 almost 8 yearsWhy don't you just use
MockBackend, BaseRequestOptions
instead of{ provide: MockBackend, useClass: MockBackend }, { provide: BaseRequestOptions, useClass: BaseRequestOptions },
. Aren't these two already provided byHttpModule
(I'm pretty sure they are). Alsoproviders: [ { 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 almost 8 yearsSo
providers: [{provide: XHRBackend: useExisting: MockBackend}, SomeService]
should give you the same result. -
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 injectingMockBackend
type. So I changed it toMockBackend
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 almost 8 yearsAs 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 almost 8 yearsSo why do I get ApiConnectorService parameter not resolved? and why only when I start both tests?
-
Paul Samsotha almost 8 yearsMaybe 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 almost 8 yearsCheck 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 almost 8 yearsCan you post a complete example. Enough to reproduce the problem. Something like I did above.
-
Supamiu almost 8 yearsI 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 almost 8 yearsWhat 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 almost 8 yearsYou can work off of this gist.github.com/psamsotha/2cce26fe4c9a3e73b8c8cbaa0fd738be, which works fine
-
Paul Samsotha almost 8 yearsI 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 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 over 7 yearsMy service constructor looks like
constructor(private http: Http, baseName: string) {
, so where I need to modify in the above code? -
Stephen almost 4 yearsThank you. Nothing in the error message to help figure this out. The key is "emitDecoratorMetadata": true