Mocking Injected Service and BeforeEach Concepts

Here, I have tried showing how to mock services and use them and basics of beforeEach() method by hands-on programming

This blog is a continuation of the last written blog. the link to that blog is : Click here

Some checking

Let us first check how many time the service is getting called. For that, let us put a debugger in the code as shown below.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  messages:string[]=[];
  log(message:string){
    debugger;
    this.messages.push(message);
  }

  constructor() { }
}

Now, run the test as usual and keep the source tab open in the Browser Dev tool.

NOTE Adding the links of my favorite channel which teaches how to use the Browser Dev Tools.

Then, run reload the test. Adding the screenshot for some help.

image.png So, when you go step by step in the Source and you see the debugger getting hit, that means the LoggerService is getting called.

How to solve the problem discussed in the end of the previous Blog?

SpyOn() is the answer. Let us understand with writing code. Do the changes in the test file as shown below:


describe('CalculatorService',() => {
  it('should add two numbers',() => {
    const loggerService=new LoggerService();
    spyOn(loggerService,'log');
    const calculator = new CalculatorService(loggerService);
    let result=calculator.add(1,2);
    expect(result).toBe(3);
    expect(loggerService.log).toHaveBeenCalledTimes(1);
  });
  it('should subtract two numbers',() => {
    const loggerService=new LoggerService();
    const calculator = new CalculatorService(loggerService);
    let result=calculator.subtract(5,2);
    expect(result).toBe(3);
  });
})

And, then run the tests again with source tab open in the DevTool. Here, now it will call the log() only once.

Now, do the same thing in the other it() method too, and we will see that in the output, LoggerService is not getting yielded at all. This is happening because we are spying on the LoggerService. And thus, Jasmine will not call the original Service it need to call but stops it. But, suppose we want to know how many times the log() method is called, we could use something called and.callThrough() like shown:

it('should subtract two numbers',() => {
    const loggerService=new LoggerService();
    spyOn(loggerService,'log').and.callThrough();
    const calculator = new CalculatorService(loggerService);
    let result=calculator.subtract(5,2);
    expect(result).toBe(3);
    expect(loggerService.log).toHaveBeenCalledTimes(1);
  });

NOTE: Don't forget removing .and.callThrough() from the code before moving forward.

So, till now we were thinking that the LoggerService is not getting called. Let us add constructor in the LoggerService.

export class LoggerService {
  messages:string[]=[];
  log(message:string){
    debugger;
    this.messages.push(message);
  }

  constructor() {
    debugger;
    // we have to check whether the constructor is being called or not.
    // Assume, some logic which is getting executing here whenever 
   }
}

Now, run the code again with the Source tab open in the Dev tool and you will see that the constructor is getting called. You will see it is getting called thrice and this is because of the logger.service.spec.ts test file. We will figure something out for that later.

Link to code till now: Repo

But for now, we have understood that now the constructor is getting called and not the log() because we are spying on the method. But, what if we don't want the whole service to be called in the first place? We have to use mock to do the same. We will mock the LoggerService.

Now, we have to create a dummy LoggerService. We will be able to do the same using createSpy. We do it like this:

let mockloggerService=jasmine.createSpyObj('loggerService', ['log']);
//this is a mock loggerService

Here, we put the log() method in the []. So, whatever method we have in the service Whenever we create a spyObj(), we don't have to use spyOn() as it will be called automatically. We just have to call the mockLoggerService which we have created as shown:


describe('CalculatorService',() => {
  it('should add two numbers',() => {
    let mockLoggerService=jasmine.createSpyObj('LoggerService',['log']);
    const calculator = new CalculatorService(mockLoggerService);
    let result=calculator.add(1,2);
    expect(result).toBe(3);
    expect(mockLoggerService.log).toHaveBeenCalledTimes(1);
  });
  it('should subtract two numbers',() => {
    let mockLoggerService=jasmine.createSpyObj('LogggerService',['log']);
    const calculator = new CalculatorService(mockLoggerService);
    let result=calculator.subtract(5,2);
    expect(result).toBe(3);
    expect(mockLoggerService.log).toHaveBeenCalledTimes(1);
  });
})

Also, remove the test code from the LoggerService.spec.test.ts and then run the test file of the Calculator again. And also, remember that we used debugger? Now, when you run the test with the source file open, you will see that the code does not go to the debugger code.

Hope, we get some idea about the Mocking concept in Testing Injectable services. Updated code: Click here

BeforeEach

Now, if you look at the code written till now, you would notice some redundancy or repetition. for example, we define the mockLogggerService twice. To avoid such repitition and make our code efficient, fast, concise, we use something called BeforeEach. Whenever we run the test case, the BeforeEach() method would run before the test case. Before Each helps us in defining resources which we know will be used at more than one places in the spec file. For example:

 let mockLoggerService:any = {};
  let calculator:CalculatorService;
  beforeEach(() => {
    let mockLoggerService = jasmine.createSpyObj('LoggerService',['logger']);
    const calculator = new CalculatorService(mockLoggerService);
  })

Now, whenever we declare these instances like this, we don't have to declare them separately in both the it() methods. It refers to the instances passed here and makes our code look concise. So, the final code would be:

describe('CalculatorService',() => {
  let mockLoggerService:any;
  let calculator:CalculatorService;
  beforeEach(() => {
    console.log('In Before each');
    mockLoggerService = jasmine.createSpyObj('LoggerService',['log']);
    calculator = new CalculatorService(mockLoggerService);
  })
  it('should add two numbers',() => {
    console.log("In the add test case");
    let result=calculator.add(1,2);
    expect(result).toBe(3);
    expect(mockLoggerService.log).toHaveBeenCalledTimes(1);
  });
  it('should subtract two numbers',() => {
    console.log("In the subtract test case");
    let result=calculator.subtract(5,2);
    expect(result).toBe(3);
    expect(mockLoggerService.log).toHaveBeenCalledTimes(1);
  });
})

Now run and check the output. Putting some console.log message in the above code, just to show that beforeEach() is called before executing any test case.

image.png

Code till now: Link