RjHiruma
RjHiruma

Reputation: 213

Unit testing with private service injected using jasmine angular2

I have a problem trying to unit test an angular service. I want to verify that this service is properly calling another service that is injected into it.

Lets say I have this ServiceToTest that injects ServiceInjected:

ServiceToTest .service.ts

@Injectable()
export class ServiceToTest  {
    constructor(private _si: ServiceInjected) {}
    public init() {
      this._si.configure();
    }

}

ServiceInjected.service.ts

@Injectable()
export class ServiceInjected {
    constructor() {}
    public configure() {
    /*Some actions*/
    }

}

With these services, now I write my unit test:

const serviceInjectedStub = {
  configure(): void {}
}


describe('ServiceToTest service Test', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [ServiceToTest ,
        { provide: ServiceInjected, useValue: serviceInjectedStub }]
    });
  });
  
  it('should be initialize the service injected', inject([ServiceToTest],
    (tService: ServiceToTest) => {
      spyOn(serviceInjectedStub, 'configure');
      tService.init();
      expect(serviceInjectedStub.configure).toHaveBeenCalled();
    }));

I expected my test to be positive, however I receive the following error:

Expected spy configure to have been called.

On the other hand, it works OK if I set the injected service public in this way:

private _si: ServiceInjected by public si: ServiceInjected

Upvotes: 21

Views: 28507

Answers (4)

Ismael Sarmento
Ismael Sarmento

Reputation: 894

From Angular 9 you should use TestBed.inject; then you don't need to add the service on providers' list.

let injectedService: ServiceInjected;
beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [ServiceToTest]
  });
  injectedService = TestBed.inject(ServiceInjected);
});

Then spy on it

// if you want to ignore the call
spyOn(injectedService, 'configure').and.callThrough();
// if you need to set stuff up
spyOn(injectedService, 'configure').and.callFake(async () => {/* some fake stuff */});

And do the checks

expect(injectedService.configure).toHaveBeenCalled();
// ...

Upvotes: 2

hzitoun
hzitoun

Reputation: 5832

Or you can use jasmine.createSpyObj and provide it with useValue like bellow:

describe('YourComponent', () => {

  let serviceInjectedSpy: jasmine.SpyObj<ServiceInjected>;

  beforeEach(async(() => {

     // notice here
     serviceInjectedSpy = jasmine.createSpyObj('ServiceInjected', ['configure']);

     TestBed.configureTestingModule({
        declarations: [YourComponent],
        providers: [
           {provide: ServiceInjected, useValue: serviceInjectedSpy}
        ],
        imports: [
         ...
        ]
     }).compileComponents().then(() => {
        fixture = TestBed.createComponent(YourComponent);
        component = fixture.componentInstance;
     });
  });

  it('should assert my test', () => {
       serviceInjectedSpy.configure.and.returnValue(/* what you want */);
       component.init();
       expect(serviceInjectedSpy.configure).toHaveBeenCalled();
  });

});

Upvotes: 9

user4676340
user4676340

Reputation:

You don't spy on the service tied to your TestBed. Get the service from your Testbed

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [ServiceToTest ,
      { provide: ServiceInjected, useValue: serviceInjectedStub }]
  });
  injectedService = TestBed.get(ServiceInjected);
});

And test on it

spyOn(injectedService, 'configure').and.returnValue(/* return same data type here */);
// ...
expect(injectedService.configure).toHaveBeenCalled();

Upvotes: 47

Swoox
Swoox

Reputation: 3740

Use this:

spyOn(serviceInjectedStub, 'configure').and.returnValue(config); // config is a mock

Upvotes: 1

Related Questions