24

I am writing test cases for angular2 components.

I had created a service which uses observable stream as below:

import {Injectable}      from '@angular/core'
import {Subject} from 'rxjs/Subject';
import {User} from './user.model';

@Injectable()
export class UserService {

  selectedUserInstance:User = new User();

  // Observable selectedUser source
  private selectedUserSource = new Subject<User>();

  // Observable selectColumn stream
  selectedUser$ = this.selectedUserSource.asObservable();

  // service command
  selectUser(user:User) {
    this.selectedUserInstance=user;
    this.selectedUserSource.next(user);
  }
}

Now In my component I have subscribed to this stream as :

getSelectedUser() {
    this.subscriptionUser = this.userService.selectedUser$.subscribe(
      selectedUser => {
        this.selectedUser = selectedUser;
      }
    );
}

Now in my spec.ts file, I want to mock this stream as :

spyOn(userService, 'selectedUser$')
        .and.returnValue(Observable.of({
            'name': 'bhushan',
            'desc': 'student'
        }));

But it keeps giving me following error:

Error: spyOn could not find an object to spy upon for selectColumn$()

is there any way to do this?

I am stuck on this issue for very long time now.

any inputs?

thanks

Bhushan Gadekar
  • 13,485
  • 21
  • 82
  • 131

1 Answers1

31

selectedUser$ isn't a method, so you can't spy on it. Instead, if you want you can just assign it a your observable

rapidColumnService.selectedUser$ = Observable.of({
  'name': 'bhushan',
  'desc': 'student'
})

NOTE (UPDATE): see updated usage of of here: Property 'of' does not exist on type 'typeof Observable

But honestly, if that is your complete service, I don't see why you even need to mock it. It is simple enough where using the real service probably wouldn't hurt. If you use the real service, then you can just selectUser whenever you want to emit something new to the component under test.

UPDATE

Another thing you could also do is instead of using an Observable is to use a Subject. A Subject is also an Observable, but it lets you emit values, making it easier to mock values to test.

rapidColumnService.selectedUser$ = new BehaviorSubject<any>();

Then when you want to send a value

rapidColumnService.selectedUser$.next({
  'name': 'bhushan',
  'desc': 'student'
});

Depending on how you have your component set up and the subscription, you may just want to use a plain Subject instead of a `BehaviorSubject. See this post for more info.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • you're GOD, saved my time +1 – Ravi Ubana Sep 13 '19 at 17:31
  • What should my expect statement say? – London804 Aug 27 '20 at 02:19
  • Using a subject might work for the test but provide unneeded access to fire and other methods right? – habla2019 Nov 30 '21 at 17:37
  • It's nicer to create a new, different BehaviorSubject and using the BehaviorSubject.asObservable() as selectedUser$ value. In this way the original type won't be changed. Similarly to the original service version. – gsziszi Sep 16 '22 at 13:57
  • This is not a correct answer (at least the first part). By using 'of', you create a synchronous observable, that will behave differently in tests if compared to async observable. To correctly mock an async Observable stream you have to use scheduler like this: `scheduled(of(value), asapScheduler)` – Yaroslav Larin Apr 28 '23 at 19:14