Contents

How to Implement Mock With Spectator With Jest

How to Implement Mock With Spectator With Jest

What I learned about Mocking with Spectator Tests

https://cdn-images-1.medium.com/max/800/1*psAjSrWlIdEqTUbfgSgMoQ.jpeg

Source: Unsplash

I mentioned in myprevious blog postthat Spectator is currently my go-to test tool for my Angular Apps. In the past couple of weeks, I’ve learned a few things about mocking using Spectator which I hope you will find useful.

Note that I’m using Spectator with Jest.

Remove unused providers

In mycreateServiceFactoryorcreateComponentFactory, I have declared my dependencies to be automatically mocked but I still kept the providers withuseValue: {}. In the example below, myUserApiServiceis already automatically mocked. In this case, it’s no longer necessary to declareUserApiServicein the providers withuseValue: {}:

let spectator: SpectatorService<UserService>; const createService = createServiceFactory({ service: UserService, providers: [ { provide: UserApiService, useValue: {}}, // TODO: Remove, we don't need it. ], mocks: [UserApiService], // Automatically mock }); beforeEach(() => (spectator = createService()));

We don’t needUserApiServicein the providers:

let spectator: SpectatorService<UserService>; const createService = createServiceFactory({ service: UserService, mocks: [UserApiService], }); beforeEach(() => (spectator = createService()));

KeepingUserApiServicein the providers doesn’t have any side-effect. However, it’s best to remove it if it’s not used.

andReturn() vs mockReturnValue()

I’ve been exclusively usingandReturn()to mock methods within a test case since I started using Spectator. I only found out recently about Jest’smockReturnValue(). It works the same way asandReturn(). The main difference I noticed is thatandReturn()is not strict with its return type.

Given a method that returns the typeObservable<User>.

class UserService { getUser(): Observable<User> { // code here.. }}

WhereUseris:

interface User { firstName: string; lastName: string;}

UsingmockReturnValue, I will get an error if I don’t mock using the expected return type:

const userUservice = spectator.inject(UserService);userService.getUser.mockReturnValue(of('user mock'));

Error:

error TS2345: Argument of type 'Observable<string>' is not assignable to parameter of type 'Observable<User>'.

mockReturnValue()accepts the method’s declared return type only:

const userUservice = spectator.inject(UserService);userService.getUser.mockReturnValue(of({firstName: 'First', lastName: 'Last'} as User));

UsingandReturn(), I can use a different type:

const userUservice = spectator.inject(UserService);userService.getUser.andReturn(of('user mock')); // I can mock with a string type!

Mocking by direct assignment

I have usedandReturn()(and soonmockReturnValue()) in test cases if I care about the return value of the mock. I find it easier to spot when I’m looking for the mocks that are happening in a single test.

However, there are test cases where I’m only interested in assertingtoHaveBeenCalled()to a mocked method. In that case, I’d usually assignjest.fn()to the method that I want to assert. This only works if the property or method isnotread-only.

If I’m usingUserServicein my component.

constructor(private userService: UserService) {}

I can mock itsgetUser()method call in a test by assigning the mock function directly. Then assert with atoHaveBeenCalled():

spectator.component['userService'].getUser = jest.fn();//.. some code hereexpect(spectator.component['userService'].getUser).toHaveBeenCalled();

Mocking read-only properties

What if the method I want to mock is read-only?

I have a getter in my service:

export class UserService { //.. some code get canAccess$(): Observable<boolean> { // .. implementaion }}

I have a component that uses the above getterUserService.canAccess$. When I try to mock that getter usingandReturn():

const userService = spectator.inject(UserService);userService.canAccess$.andReturnvalue(of(false));

I get the following error:

TS2339: Property 'andReturn' does not exist on type 'Observable '.

I also can’t assign the mock directly becausecanAccess$here is read-only:

spectator.component['userService'].canAccess$ = of(false);
Cannot assign to 'canAccess$' because it is a read-only property.

There are a few ways to handle this.

Using Object.defineProperty()

I previously usedObject.definePropertyto modify the service object’s property:

Object.defineProperty(spectator.component['userService'], 'canAccess$', { 
  value: of(true) 
});

This worked. I thought there must be a way to achieve this by using Spectator rather than modifying the service object directly, see the next section.

Setting useValue in a test case

I found out about this approach when I was browsing throughthe examplesin Spectator’s README.

I can declare a default mock forcanAccess$in mycreateComponentFactorycall by settinguseValue.

const createComponent = createComponentFactory({ 
  component: MyComponent, 
  //...typeOrOptions here 
  providers: [ 
    { 
      provide: UserService, 
      useValue: { 
        canAccess$: of(true) 
      } 
    } 
  ], 
});

All tests in a suite will use this default value ofcanAccess$unless I override it within a test or another test suite. To override the default mock, I can specify the provider in the test case with auseValue. In this example, I change the return value toof(false).

const provider = { 
  provide: UserService, 
  useValue: { 
    canAccess: of(false) 
  }, 
};

Then callcreateComponent()using the provider with the mock override that I just declared.

spectator = createComponent({ 
  providers: [provider], 
});

I can override the default mock in individual test cases.

it('should prevent access...', () => { 
  const provider = { 
    provide: UserService, 
    useValue: { 
      canAccess$: of(false) 
    }, 
  }; 
  spectator = createComponent({ 
    providers: [provider], 
  }); 
  //.. code here
});

Or I can declare the override inbeforeEach()if I want to use it in a test suite:

describe('Prevent access', () => { 
  beforeEach(() => { 
    const provider = { 
      provide: UserService, 
      useValue: { 
        canAccess$: of(false) 
      }, 
    }; 
    spectator = createComponent({ 
      providers: [provider], 
    }); 
    //.. code here 
  }); 
  it('should prevent access...', () => { 
    // .. code here 
  }); 
  // .. more tests
});

If you like this story, you might also enjoy my other stories about Spectator Test and Angular: