angularunit-testingrxjsjasminebehaviorsubject

Unit testing a compnent that is getting data from Angular Behavior Subject


I have this service called EmployeeService where I am using Angular's BehaviorSubject to get data from an api. I'm subcribing to this BehaviorSubject from another component which I am trying to test now. But because I'm unable to mock the data from the service mentioned above, the unit test is not working.

This is a bare bones of the EmployeeService:

class EmployeeService extends ApiService {
  public isEmployeeSub: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

  someMethod() {
    this.isEmployeeSub.next(true);
  }

  // other methods
}

Now this is the component I'm trying to test. Please note that this is a huge component but I'm just showing the most important part which is needed for this component to work and that is the isEmployee data coming from EmployeeService.

class SalaryComponent implements OnInit, OnDestroy, AfterViewInit {
  private employeeService: EmployeeService;

  employee: EmployeeModel;
  employeeSubscription: Subscription;

  constructor(employeeService: EmployeeService) {
    this.employeeService = employeeService;

    this.employeeSubscription = this.employeeService.isEmployee.subscribe(isEmployee: boolean => {
      
      if (!isEmployee) {
        // I want this part to run at some tests and not at the others.
        return;
      }

      // do some other things
    });
}

The above component is subscribing to BehaviorSubject in EmployeeService, gets the data if it an employee and runs some other things. If not isEmployee then it returns. I want that conditional in subscribe to run at some tests and not at the others.

describe('SalaryComponent', () => {
  let component: SalaryComponent;
  let fixture: ComponentFixture<SalaryComponent>;
  let isEmployeeSubject = new BehaviorSubject<boolean>(false);
  let mockEmployeeService: jasmine.SpyObj<EmployeeService>;  


  beforeEach(async () => {
    mockEmployeeService = jasmine.createSpyObj<EmployeeService>(
      'EmployeeService', 
      [], 
      {
        isEmployeeSub: isEmployeeSubject
      }
    );

    await TestBed.configureTestingModule({
      imports: [],
      declarations: [],
      providers: [],
    })
      .compileComponents();
  });

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

  fit('should show drop down if it is employee', async ()  => {
     mockEmployeeService.isEmployeeSub.next(true);
  }

  fit('should not show drop down if it is employee', async ()  => {
     mockEmployeeService.isEmployeeSub.next(false);
  }
}

This is how I wrote a test previously with ReplaySubject. That works perfectly fine. But this one doesn't seem to work. The value isEmployee is always false somehow.


Solution

  • It seems your not using the asynchronous features correctly.
    Do not use the javascript async.

    The easiest way is to use fakeAsync this way:

    fit('should show drop down if it is employee', fakeAsync(()  => {
      mockEmployeeService.isEmployeeSub.next(true);
    
      tick(); // triggers subscribes
      expect(...).toBe(...);
    });
    
    fit('should not show drop down if it is employee', fakeAsync(()  => {
      mockEmployeeService.isEmployeeSub.next(false);
    
      tick(); // triggers subscribes
      expect(...).toBe(...);
    });
    

    2nd issue, your isEmployeeSubject is shared among all your tests. So in your second test from above, what happens is isEmployeeSubject remembers the true value from previous test. So in your component constructor, the subscribe first triggers with a true.
    Then, you run your test, and do a .next(false), which re-triggers your subscribe. Result: your subscribe has both been triggered with true and false value.
    Solution: Reset the BehaviorSubject in your beforeEach:

    beforeEach(async () => {
      isEmployeeSubject = new BehaviorSubject<boolean>(false);
      mockEmployeeService = jasmine.createSpyObj<EmployeeService>(
        'EmployeeService', 
        [], 
        {
          isEmployeeSub: isEmployeeSubject
        }
      );
      // ...