How mock object work in Angular jasmine unit test
Asked Answered
V

3

5

I am learning unit testing and Angular, so I am beginner in both. I have referred several tutorials and articles available on unit testing http in angular.

I am not able to understand that what does httpMock does using HttpTestingController. We call function of actual service then why we call it mock? What is underlying process? Please refer some article to get better understanding.

Thanks in advance.

Edit: This is the issue where I stuck with httpMock.

Valer answered 20/4, 2018 at 13:6 Comment(0)
M
10

Let's take one of my file as an example, shall we ?

import { SimpleConfiguration } from '../../../../../model/SimpleConfiguration';
import { SessionService } from '../../../session/session.service';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ConfigurationService } from './configuration.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('ConfigurationService', () => {

  let httpMock: HttpTestingController;
  let service: ConfigurationService;

  const createFakeFile = (fileName: string = 'fileName'): File => {
    const blob = new Blob([''], { type: 'text/html' });
    blob['lastModifiedDate'] = '';
    blob['name'] = fileName;
    return <File>blob;
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      providers: [ConfigurationService, SessionService]
    });

    httpMock = TestBed.get(HttpTestingController);
    service = TestBed.get(ConfigurationService);
  });

  it('should be created', done => {
    expect(service).toBeTruthy();
    done();
  });

  it('getConfigurations should GET on postes-sources/:postID/configuration', (done) => {
    service.getConfigurations(0).subscribe(res => done());
    const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
    expect(successRequest.request.method).toEqual('GET');
    successRequest.flush(null);
    httpMock.verify();
  });

  it('uploadFile should POST on postes-sources/:postID/configuration', (done) => {
    service.uploadFile(0, createFakeFile(), new SimpleConfiguration()).subscribe(res => done());
    const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
    expect(successRequest.request.method).toEqual('POST');
    successRequest.flush(null);
    httpMock.verify();
  });

  it('updateComment should POST on postes-sources/:postID/configurations/:confID', (done) => {
    service.updateConfiguration(0, 0, new SimpleConfiguration()).subscribe(res => done());
    const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations/0');
    expect(successRequest.request.method).toEqual('POST');
    successRequest.flush(null);
    httpMock.verify();
  });

  it('getComponentInformations should GET on postes-sources/:postID/tranches/:trancheID/parametres', (done) => {
    service.getComponentInformations(0, 0).subscribe(res => done());
    const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/tranches/0/parametres');
    expect(successRequest.request.method).toEqual('GET');
    successRequest.flush(null);
    httpMock.verify();
  });
});

Let me explain in details to you, step by step.

  1. We start by describe-ing our test. it allows us to group our tests. In this case, we group our tests by feature, and our feature is a service called ConfigurationService. We then provide a callback function : this is the function that will be ran by Jasmine to run your tests.

  2. Next, we declare our variables. Here, we declare 2 variables, and one function : httpMock, service, and createFakeFile(). Those variables will be helpful during the whole test group, so we declare them at the top level.

  3. Then comes the beforeEach : before every test, this function will be ran, to do something. In this one, it will create a TestBed : this is some boilerplate code that will create some kind of Angular module, to allow your tests to run as if your feature was in a real Angular application.

In this test bed, you need to declare your dependencies : since I'm testing an HTTP service, I have to import the HTTP testing module, and because my test uses routing, I also have to import the router testing module. I laso need to import the service being tested, and I import a SessionService because I use it in my service too.

After that, I get the service instances of those dependencies through TestBed.get : this will allow me to spy on their properties, and see their values and if they're called. This will also allow me to call the functions I want to test.

  1. There comes the first tests. The first one is pretty simple and is implemented by default : I test if the service is getting created correctly. If not, it means you're lacking a dependency, or you mocked your dependencies wrong. The expect(service).toBeTruthy() is the real test : with expect, jasmine expects (duh) the service to be truhty (i.e. it should not be equal to undefined or null).

  2. The next test is a real test : during this test, I expect my function to call a certain endpoint, with a certain verb.

I start by saying that once the call has been made, I have to end the test. This is done by calling done, which is the callback given just above.

Then, I mock an HTTP call : it means I make Angular believe I'm making an HTTP call, but I actually pretend to. But in its eyes, it's like making a real one : this is what a mock is : you simulate a behavior or a value. As you can see, I mock an HTTP call on a particular endpoint, and I expect a particular HTTP verb.

In the end, if my function getConfigurations GETS on that URL, my test will succeed, and if not, it will fail. This means that if I ever change the endpoint in my actual service, the test will fail : this test prevents side effects.

The other tests are the same as that, so I don't need to explain them I guess.

If you wonder, I didn't do it alone, just like you i asked for help and followed tutorials. But once you get used to it, it becomes very fast and easy to test your features.

I hope this helps, and feel free to ask anything you want me to explain !

Margarita answered 20/4, 2018 at 13:43 Comment(4)
trichetriche Thanks a lot for explaining each line in detail. This exactly what I was looking for. Thanks a ton for your time and sharing your knowledge. I have posted this question #49940714. I am confused with one thing that we call actual function of our service and then call actual url and what is used of creating mock data when I asserting actual data returned by service? For eg: as I did in my post: expect(comments).toBe('json'); expect(comments).toContain('comments');.Valer
What is usage of mockhttp(HttpClientTestingModule, HttpTestingController) here? Sorry but I am feeling we could right same logic without using this module.Valer
I'm going to answer it then !Margarita
That will be awesome then. Thanks!Valer
A
1

The idea of mocking service is that you don't need to use the actual functionality (the real http call), but you do need the service methods to run your code without any exceptions.

For example: You have an component which in some point collect data from some API via Http. One of the unit test should test if you component make that call, but you are don't give a damn if there is a real call there. The point of your unit test is to test if the call has happened.

Edit: The same will happen if some of the inner logic make that call to collect a data to display something. You should mock the http call and return a data of your own. Your unit test shouldn't relly something from the outside. Imagine that your test will run on a enviroment without internet. The test should pass any time.

This scenario applies, no matter of the service you testing. The unit tests should have single responsability. They should not depend on anything different than their main purpose.

Are answered 20/4, 2018 at 13:39 Comment(0)
T
1

I have actually just ran into this issue in the past week so this is pretty fresh in my head. Jasmine confused me for a bit as well so I can share what I did to fix my issue.

To start, the Angular tutorials misleads new testers a little bit. It claims that we should use Jasmine, but then begins using TestBed and it is a little misleading. I ultimately went with TestBed and I can show you what I used.

So you have your describe:

descripe('randomService', () -> {

}

You need to initialize your `randomService':

descripe('randomService', () -> {
    let randomService: RandomService;         
}

using beforeEach(), you can re-init, assign values, etc. before each it statement within your describe.

descripe('randomService', () -> {
   let randomService: RandomService;         
   beforeEach(() => {
       imports: [
           HttpClientTestingModule
       ]
   });
}

So I am telling Angular to re-import the HttpClientTestingModule before each it block. My randomService requires HttpClient so I need to create a Jasmine Spy object, that returns what I tell it to instead of letting my randomService hit the actual backend and alter real data in my backend.

descripe('randomService', () -> {
   let randomService: RandomService;
   httpClientSpy;

   beforeEach(() => {
       imports: [
           HttpClientTestingModule
       ]
   });
   httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
   randomService = new RandomService(<any> httpclientSpy);       
}

So now whenever I do a 'get' method within my randomService, it will really be using the httpClientSpy, and it compiles because I told randomService that my argument was of type 'any' and to its' best knowledge, that is in fact a real HttpClient even when it is not. To use this properly, you must set up a fake return for your fake get:

descripe('randomService', () -> {
   let randomService: RandomService;
   httpClientSpy;
   mockResponse = { 1: ['first', 'second', 'third'] };
   beforeEach(() => {
       imports: [
           HttpClientTestingModule
       ]
   });
   httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
   randomService = new RandomService(<any> httpclientSpy);       
});

    it('should return first, second, third', () => {
        spyOn(httpClientSpy, 'get').and.returnValue(Observable.of(mockResponse));
        // randomService. <your get method here...>
        randomService.getValue().subscribe((response) =>
            expect(resposne[0].length).toEqual(3);
};
});

And that response should be the mockResponse that was created in our beforeEach() It does not have to be in the beforeEach(), but in this example I left it in there.

Theatre answered 20/4, 2018 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.