英文:
Unit testing a compnent that is getting data from Angular Behavior Subject
问题
我有这个名为EmployeeService
的服务,在这个服务中,我正在使用Angular的BehaviorSubject
来从API获取数据。我从另一个组件中订阅了这个BehaviorSubject
,现在我正在尝试测试它。但是因为我无法模拟上述服务中的数据,单元测试无法正常工作。
这是EmployeeService
的基本结构:
class EmployeeService extends ApiService {
public isEmployeeSub: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
someMethod() {
this.isEmployeeSub.next(true);
}
// 其他方法
}
现在这是我要测试的组件。请注意,这是一个庞大的组件,但我只显示了最重要的部分,这是该组件工作所需的部分,即来自EmployeeService
的isEmployee
数据。
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) {
// 我希望这部分在某些测试中运行,而在其他测试中不运行。
return;
}
// 进行一些其他操作
});
}
上述组件正在订阅EmployeeService
中的BehaviorSubject
,如果它是员工,则获取数据并执行一些其他操作。如果不是isEmployee
,则返回。我希望在某些测试中运行这个订阅中的条件,而在其他测试中不运行。
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);
}
}
这是我以前使用ReplaySubject
编写的测试方式。那个方法完全正常。但是这个测试似乎不起作用。一些原因使isEmployee
的值始终为false。
英文:
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.
答案1
得分: 1
看起来你没有正确使用异步特性。
不要使用JavaScript中的async
。
最简单的方法是使用fakeAsync
,如下所示:
fit('如果是员工应该显示下拉框', fakeAsync(() => {
mockEmployeeService.isEmployeeSub.next(true);
tick(); // 触发订阅
expect(...).toBe(...);
});
fit('如果不是员工不应该显示下拉框', fakeAsync(() => {
mockEmployeeService.isEmployeeSub.next(false);
tick(); // 触发订阅
expect(...).toBe(...);
});
第二个问题,你的isEmployeeSubject
在所有的测试中都是共享的。因此,在上面的第二个测试中,发生的情况是isEmployeeSubject
记住了上一个测试的true
值。因此,在你的组件构造函数中,订阅首先会触发一个true
。
然后,你运行你的测试,执行.next(false)
,这将重新触发你的订阅。结果:你的订阅已经被触发了true
和false
的值。
解决方法是在每个测试的beforeEach
中重置BehaviorSubject
:
beforeEach(async () => {
isEmployeeSubject = new BehaviorSubject<boolean>(false);
mockEmployeeService = jasmine.createSpyObj<EmployeeService>(
'EmployeeService',
[],
{
isEmployeeSub: isEmployeeSubject
}
);
// ...
});
英文:
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
}
);
// ...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论