angularkarma-jasmineangular2-servicesangular2-testingangular2-injection

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


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);

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).

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.


Solution

  • 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');
    	});
      })));
    });