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.
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
}
);
// ...