5

I have a service with a replay subject.

export class UserService {
  public userChanged: ReplaySubject<User> = new ReplaySubject<User>();

...
public getUser(userId?): void {
    ...
    this.http.get(url, httpOptions).pipe(
      catchError(this.handleError('getUser', null, 'Couldn\'t get user', options))
    ).subscribe( (user: User) => {

       this.userChanged.next(user);

    });
  }

My component subscribes to userChanged.

this.userService.userChanged.subscribe((user) => {
  this.user = user;
});

Now, I want to mock my UserService in the component test:

1 option Testing Observables in Angular)

import { of } from 'rxjs';
...
    const userServiceSpy = jasmine.createSpyObj('UserService', {'userChanged': of({_id: '1'}) });

or 2 option)

    const userServiceSpy = jasmine.createSpyObj('UserService', {'userChanged': () => of({_id: '1'}) });

or 3 option angular test tutorial)

const userServiceSpy = jasmine.createSpyObj('UserService', ['userChanged']});
const userChangedSpy = userServiceSpy.userChanged.and.returnValue( of({_id: '1'})  );

+

TestBed.configureTestingModule({
  ...
  providers: [
    ...
    {provide: UserService, useValue: userServiceSpy}
  ],
  schemas: [NO_ERRORS_SCHEMA]
})

give me this err:

this.userService.userChanged.subscribe is not a function

Shouldn't of return an Observable to subscribe to?

Question: How to mock this?

Andi Giga
  • 3,744
  • 9
  • 38
  • 68

2 Answers2

12

createSpyObj is used to to create spies on methods. You could use it for getUser method of UserService.

userChanged is just a property of the class. You don't need a spy for it.

What you can do is simply create a mock object that returns subject:

const userChanged = new Subject();

 providers: [
    ...
    {provide: UserService, useValue: { userChanged }}
  ],

{ userChanged } is equal to { userChanged: userChanged }

Then, in your beforeEach block you would emit a new user instance:

//...
beforeEach(() => {
   const myUser = new User(...)
   userChanged.next(myUser)
})

I recommend to do this in the beforeEach block to avoid side effects between different specs.

 providers: [
    ...
    {provide: UserService, useValue: { userChanged: of({id: 1}) }}
  ],

Another way of doing the same would be simply creating observable using of method same way you're doing it in your example.


If you really want to spy on subscribe method, you can create spy on it:

spyOn(userChanged, 'subscribe')


If you want to mix spyObject with properties, you can use spread operator:

const spyObj = {
  ... jasmine.createSpyObj('MyObject', ['spyMethod']),
  myProperty: true,
};

spyObj.spyMethod();

expect(spyObj.spyMethod).toHaveBeenCalled();
expect(spyObj.myProperty).toBeTrue();
DDRamone
  • 1,078
  • 11
  • 18
  • I had to change `.emit` to `.next` but it worked. Thanks. If I want to use this and spy on methods can I combine it e.g.: `{provide: UserService, useValue: { userChanged, userServiceSpy}}` – Andi Giga Mar 21 '19 at 11:36
  • Sorry for `.emit`. Always messing Subject with EventEmitter -_-. – DDRamone Mar 21 '19 at 11:45
  • 1
    For combining properties with spyObject you can use spread operator. Check updated answer – DDRamone Mar 21 '19 at 11:46
  • Hi @DDRamone. As per the above solution you're returning userChanged with an observable. Could you please help, when we have multiple subjects written in a service? – Karthik Bhat Jul 02 '20 at 11:31
  • @KarthikBhat You can add as many properties to the mock services as you need. Just separate them with commas as you would do in the normal object. If this doesn't answer your question, let me know more details and I will try to help. – DDRamone Jul 02 '20 at 20:19
  • @DDRamone: I was referring to the scenario when we have multiple subjects in the service that are injected to a component (and are Unit Testing the component). My question is changing the below code : providers: [ ... {provide: UserService, useValue: { userChanged }} ], to address multiple subjects, methods etc., – Karthik Bhat Jul 03 '20 at 04:31
  • @DDRamone: I was able to figure out. Please find my answer below. Thanks for the quick reply – Karthik Bhat Jul 03 '20 at 11:10
1

This might be a bit late.. But I was able to resolve this issue by following the below approach:

1. mockUserService = TestBed.get(UserService)
2. By calling the subscribe method using :
mockUserService.userChanged.Subscribe(data => {
// do something
})

Note: Code at line 1 & 2 must sit anywhere after TestBed.configureTestingModule declaration.