Search code examples
angularjasminejestjskarma-jasmineangular-test

How to cover Jasmine unit test for RxJS subject in angular


I am very new Jasmine unit test cases. My scenario may be easy but I am not sure how to cover test case for ngInit for below class. Can someone help me,

export class Component1 implements OnInit {
    details$: Observable<any>; 
    name: string; 
    error: string 

    constructor(private service1: Service1, private service2: Service2) { }

    ngOnInit() {
       this.service1.service1Subject.subscribe( info => {
            if(info['url']){
                this.details$ = this.service2.get(info['url'])
                this.details$.subscribe(
                 (info) => { this.name = info['name']}; 
                 (error) => { this.erro = error['error']}; 
                ); 
            }  
       }); 
    }
}

Test case :

describe('Component1', () => {
  let component: Component1;
  let fixture: ComponentFixture<Component1>;

  beforeEach(async(() => {
   TestBed.configureTestingModule({
     declarations: [Component1],
     imports: [
       HttpClientTestingModule, CommonModule
     ],
     providers: [Service1, Service2]
   })
     .compileComponents();
   }));

   beforeEach(() => {
    fixture = TestBed.createComponent(Component1);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should call Get Data', () => {
      const service2: Service2 = TestBed.get(Service2);
      const spy = jest.spyOn(service2, 'get').mockImplementation(() => {
          return {
             info :  [...],
              name : ''  
          }
      });
      component.ngOnInit();
      expect(spy).toHaveBeenCalled();
  });
});

The problem here is, I am not sure how to mock service1, RxJS subject. Please someone help me.


Solution

  • I see you're using Jest, but here's how I've set up component tests that use a service that expose a Subject.

    1. Create mocks for all your services
    2. Provide them in your test module
    3. Mock implementations to control the flow of data
    4. Perform your assertions

      describe('Component1', () => { 
          let component: Component1;
          let fixture: ComponentFixture<Component1>;
      
          //create mock service objects
          let service1Mock = jasmine.createSpyObj('service1', ['toString']);
          let service2Mock = jasmine.createSpyObj('service2', ['get']);
      
          beforeEach(async(() => {
              TestBed.configureTestingModule({
                  declarations: [Component1],
                  imports: [
                      HttpClientTestingModule,
                      CommonModule
                  ],
                  providers: [
                      //Set up the dependency injector, but use the mocks as the implementation
                      { provide: Service1, useValue: service1Mock },
                      { provide: Service2, useValue: service2Mock }
                  ]
              }).compileComponents();
          }));
      
          beforeEach(() => {
              //add an observable to your service
              //this will also help reset the observable between each test
              service1Mock.service1Subject = new Subject<any>(); 
          });
      
          beforeEach(() => {
              fixture = TestBed.createComponent(Component1);
              component = fixture.componentInstance;
              fixture.detectChanges();
          });
      
          it('should get the data', () => {
              //configure the mock implementation of "service2.get" to successfully return data
              //You can alternatively use "throw({error: 'some_error'})" to test your "error" case
              service2Mock.get.and.returnValue(of({name: 'some_name'}));
      
              //tell the mock to emit some data!
              service1Mock.service1Subject.next( {url: 'some_url'} );
      
              //Your component subcriptions should handle the event, perform whatever test needs to do
          });
      });
      

    I know that Jasmine was planning on making it possible to create spy objects with attributes, but I haven't actually used it myself.

    As an aside, if you're not using details$ in your template, you can eliminate the variable entirely.

    ngOnInit(){
        this.service1.service1Subject.subscribe( info => {
            if(info['url']){
                this.service2.get(info['url']).subscribe(
                    (info) => { this.name = info['name']}; 
                    (error) => { this.error = error['error']}; 
                ); 
            }  
        });
    }