loading...
Cover image for Angular Testing: Mock Private Functions

Angular Testing: Mock Private Functions

daviddalbusco profile image David Dal Busco Originally published at Medium ・3 min read

I share one trick a day until (probably not) the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Eleven days left until hopefully better days.


This week I made several progress in one of my client’s project and had therefore to write new test cases. For one of these, I notably had to mock a private function using Jest.

When I was replicating this test for the purpose of this blog post, I figured out that I was actually using Jasmine as it is the default test suite used when creating new Ionic Angular applications 😁.

That’s why I am sharing today both solutions or how to mock a private function with Jasmine or Jest 😇.


Credits

This blog post Jest’s solution has been provided by Brian Adams on Stackoverflow. The Jasmine one was inspired by the answer of jurl on the same platform too.

Kudos to them, not all heroes wear capes!


Test Setup

Once again, I am using my favorite API for the demo purpose: the free DOG Api.

Let’s then agree that our goal is to create a test for the following service which does not do much beside fetching a random dog but note that I explicitly marked the query function as a private method for demonstration purpose.

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';

interface DogResponse {
    message: string;
    status: string;
}

@Injectable({
    providedIn: 'root'
})
export class DoggosService {

    constructor(private httpClient: HttpClient) {
    }

    async findDoggo(): Promise<string | null> {
        const response: DogResponse = await this.searchDoggos();

        if (!response) {
            return null;
        }

        return response.message;
    }

    private searchDoggos(): Promise<DogResponse> {
        const url = 'https://dog.ceo/api/breeds/image/random';
        return this.httpClient.get<DogResponse>(url).toPromise();
    }
}

Failing Unit Test

Before trying to mock our private function, I thought that writing a test which fails would be a good start.

import {TestBed} from '@angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';

import {DoggosService} from './doggos.service';

describe('DoggosService', () => {
    let httpTestingController: HttpTestingController;
    let service: DoggosService;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule]
        });

        httpTestingController = TestBed.get(HttpTestingController);
        service = TestBed.get(DoggosService);
    });

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

    it('should fetch a doggo', async () => {
        const mockUrl = 'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';
        const data: string | null = await service.findDoggo();

        expect(data).not.toBe(null);
        expect(data).toEqual(mockUrl);
    });
});

Because we are performing an HTTP request and are not mocking it, the test fails on a timeout. That’s why our goal will be to solve this issue while mocking the private function which takes care of performing the request.


Mock A Private Function With Jasmine

To mock a private function with Jasmine, we can spy on our service private function searchDoggos and use a fake callback, callFake , to provide the mocked data as return when needed. Moreover, we can also test that our function has effectively been executed.

it('should fetch a doggo', async () => {
    const mockUrl = 
    'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';

    const handleSpy = spyOn(DoggosService.prototype as any, 
                            'searchDoggos');
    handleSpy.and.callFake(() => {
        return new Promise((resolve) => {
            resolve({
                message: mockUrl,
                status: 'success'
            });
        });
    });

    const data: string | null = await service.findDoggo();

    expect(data).not.toBe(null);
    expect(data).toEqual(mockUrl);

    expect(handleSpy).toHaveBeenCalled();
});

Thanks to these changes, we are now able to run our test with success 🥳.


Mock A Private Function With Jest

The Jest solution follow the same logic as the above one except that we take advantages of the mockImplementation method to mock the private function.

it('should fetch a doggo', async () => {
    const mockUrl = 
    'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';

    const handleSpy = jest.spyOn(DoggosService.prototype as any, 
                                 'searchDoggos');
    handleSpy.mockImplementation(() => {
        return new Promise(resolve =>
            resolve({
                message: mockUrl,
                status: 'success'
            })
        );
    });

    const data: string | null = await service.findDoggo();

    expect(data).not.toBe(null);
    expect(data).toEqual(mockUrl);

    expect(handleSpy).toHaveBeenCalled();
});

Summary

Even though it looks really trivial once summarized, it took me a bit of time to find these solutions and I am really grateful that both Brian and jurl posted their answers on Stackoverflow. Hopefully this might help someone some day too!

Stay home, stay safe!

David

Cover photo by Overture Creations on Unsplash

Posted on by:

daviddalbusco profile

David Dal Busco

@daviddalbusco

Creator of DeckDeckGo | Organizer of the Ionic Zürich Meetup

Discussion

pic
Editor guide
 

Once again, thanks David! I'm really enjoying and taking the most I can from this article series. Keep it up!

 

Thank you Sebastián for the warm feedback, it makes my day!
Eleven more posts to go, I gonna try my best to reach the goal 😅.