Testing with Mocks & Spies using createSpyObj in Angular

Luis Eduardo
3 min readJan 28, 2021
Photo by Lianhao Qu on Unsplash

Here I am going to explain a very simple way to write test the services injected in your Angular component.

The one thing I hate in a dev team is when some other developer says in this standup “The feature is done, I only need to write the tests”. I always want to say out loud — “Well, then the task is not done yet”. But I just keep that for myself, as I don’t want to be the typical arrogant toxic developer.

So If you came to this post, is because you know the importance of testing. I am glad. But writing tests for a component can be annoying and tricky when you have dependencies that you need to inject.

First of all, you should not add the Real Service class in the provider’s array, that’s not a good practice. Always use mocks.

The Problem

Every time you need to test a service that is being used in your component, you need to create a mocked service class and overwrite the methods inside.

You will always hear about creating a mock service for your tests and normally you will create a mocked class like this:

class MockUserService { 
getFirstName() {
return of('Joe');
}
getLastName() {
return of('Rogan');
}
}

Then you will add this service to the provider’s array:

TestBed.configureTestingModule({providers: [
{
provide: UserService,
useValue: MockUserService
}
]});

That feels like too much boilerplate if you have many services injected into your component.

Using createSpyObj

There is a simpler way to mock services by creating a Spy Object.

A Spy is a feature of Jasmine that allows you to stub any function and track calls to it back. It’s usually used to mock a function or an object.

But let’s not confuse a spy with a spyObj.

jasmine.createSpy: can be used when there is no function to spy on.

jasmine.createSpyObj: it’s used to create mocks that will spy on methods. It will return an object for each property defined in the spy.

Example:

describe('MyComponent', () => {const mockedUserService = jasmine.createSpyObj('MyRealService', ['getFirstName', 'getLastName', 'getId']);beforeEach(() => TestBed.configureTestingModule({providers: [{
provide: UserService,
useValue: mockedUserService
}]
}));});

So, the createSpyObj takes as arguments the name of the Service we want to mock, and then in the second argument, we define in an array all the methods inside that class that we want to mock.

in this case, we have 3 methods we want to test. getFirstName, getLastName and getId.

If we need this service to return some values to go on with our tests, we should be able to set a value that these services need to return.

This is how it’s done:

mockedUserService.getFirstName.and.returnValue(of('Joe'))

remember that in Angular we use Observables, so for our mock service, we need to return an observable of a string using the rxjs operator of .

We can do the same thing for all our methods if we need to.

mockedUserService.getFirstName.and.returnValue(of('Joe'));
mockedUserService.getLastName.and.returnValue(of('Rogan'));
mockedUserService.getId.and.returnValue(of(123));

And… that’s it!

And now you also have a Spy to which you can write your assertions for:

it('should call getFirstName', () => {    
expect(mockedUserService.getFirstName).toHaveBeenCalled();
});

Depends really on the use case you want to use it.

Writing test it’s important, but also annoying. We should never inject the real service class into our testbed, we should always create a mock or stub service.

Mocking a service can be done very simply and quickly using createSpyObj. It creates a stub class and a spy in a single line of code.

If you have any comments or questions, please share them in the comment section.

--

--