2

I have one service which has the below function

injectService(serviceToInject: string, methodToInvoke: string){
    let service = this.$injector.get(serviceToInject);
    service[methodToInvoke]();
}

I was wondering how I could test this ? I tried this :

(function () {
'use strict';

describe.only('ServiceA tests', function () {
    let ServiceA;

    beforeEach(angular.mock.module('main'));

    beforeEach(inject(function (_ ServiceA_, _$injector_) {
        ServiceA = _ServiceA_;
        $injector = _$injector_;
    }));

    describe.only('injectServiceAndInvoke', function () {
        it('given a string serviceToInject which is a valid service name and a string methodToInvoke which is valid method name without parameters, it should inject the service and call the method', () => {

            let serviceName = 'validServiceName';
            let methodWithoutParams = 'method';
            let injectedService = $injector.get(serviceName);
            // sandboxSinon.stub(ButtonService.$injector, 'get').withArgs(serviceName).returns(stubbedService);

            let methodToBeCalled = sandboxSinon.stub(injectedService, methodWithoutParams).withArgs(undefined);


            sandboxSinon.stub(ServiceA, 'tokenizeState').withArgs(methodWithoutParams).returns([methodWithoutParams, undefined]);
            ServiceA.injectServiceAndInvoke(serviceName, methodWithoutParams);
            expect(methodToBeCalled.calledOnce).to.equal(true);
        });

    });

});

})();

And I got as error (correctly) that the service 'validServiceName' does not exist. I tried also to stub the $injector.get but I don't understand what should return this stub and how to invoke the method from this service.

geo
  • 2,283
  • 5
  • 28
  • 46
  • Please, provide more code from test, so the answer could take it into account. – Estus Flask May 08 '17 at 15:06
  • Updated with the test – geo May 08 '17 at 15:11
  • As an aside, this feels very much like a service locator. Why do you need to do this and are you testing your implementation or are you testing the framework? – David L May 08 '17 at 15:12
  • I want to have an abstract way to inject services from buttons, so I wrote this code and I want to test it properly. – geo May 08 '17 at 15:13

1 Answers1

3

Since $injector service is used globally, it can't be mocked completely via DI. This is an obstacle for truly isolated unit testing. But not really a bad thing since a single conditional mock doesn't make the test fragile:

const injectedService = { methodName: sinon.stub() }; 
sinon.stub($injector, 'get');
$injector.get.withArgs('injectedServiceName').returns(injectedService)
$injector.get.callThrough();
ServiceA.injectServiceAndInvoke('injectedServiceName', 'methodName');

expect($injector.get.withArgs('injectedServiceName').calledOnce).to.equal(true);
expect(injectedService.methodName.calledOnce).to.equal(true);
expect(injectedService.methodName.calledWith()).to.equal(true);

But since the service has $injector as a property, this provides a good option for testing because the property can be mocked after service instantiation instead of mocking real $injector.get:

const injectedService = { methodName: sinon.stub() }; 
const injectorMock = { get: sinon.stub() };
injectorMock.get.withArgs('injectedServiceName').returns(injectedService);
ServiceA.$injector = injectorMock;
ServiceA.injectServiceAndInvoke('injectedServiceName', 'methodName');

expect(injectorMock.get.withArgs('injectedServiceName').calledOnce).to.equal(true);
expect(injectedService.methodName.calledOnce).to.equal(true);
expect(injectedService.methodName.calledWith()).to.equal(true);
Estus Flask
  • 206,104
  • 70
  • 425
  • 565