如何在角度单位测试取消订阅功能

Bal*_*ács 9 unit-testing subscription observable rxjs angular

我想找到一种方法来测试订阅和主题上的取消订阅功能调用.

我想出了一些可能的解决方案,但每一个都有利有弊.请记住,我不想为了测试目的而改变变量的访问修饰符.

  1. 使用反射访问组件的私有变量.

在这种情况下,我有一个私有类变量存储订阅:

component.ts:

private mySubscription: Subscription;
//...
ngOnInit(): void {
    this.mySubscription = this.store
        .select(mySelector)
        .subscribe((value: any) => console.log(value));
}

ngOnDestroy(): void {
    this.mySubscription.unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

component.spec.ts:

spyOn(component['mySubscription'], 'unsubscribe');
component.ngOnDestroy();
expect(component['mySubscription'].unsubscribe).toHaveBeenCalledTimes(1);
Run Code Online (Sandbox Code Playgroud)

优点:

  • 我可以联系到我的订阅.
  • 我可以测试是否在正确的订阅上调用了取消订阅方法.

缺点:

  • 我只能通过反射达到mySubscription,如果可能的话我想避免.

  1. 我为订阅创建了一个变量,就像在选项1中一样.但是我没有使用反射来访问变量,而是在不知道源的情况下检查是否调用了unsubscribe方法.

component.ts:与选项1相同


component.spec.ts:

spyOn(Subscription.prototype, 'unsubscribe');
component.ngOnDestroy();
expect(Subscription.prototype.unsubscribe).toHaveBeenCalledTimes(1);
Run Code Online (Sandbox Code Playgroud)

优点:

  • 我可以测试是否调用了取消订阅方法

缺点:

  • 我无法测试调用的取消订阅方法的来源.

  1. 我实现了一个辅助函数,它对传递的订阅参数调用unsubscribe方法.

subscription.helper.ts:

export class SubscriptionHelper {

    static unsubscribeAll(...subscriptions: Subscription[]) {
        subscriptions.forEach((subscription: Subscription) => {
                subscription.unsubscribe();
            });
    }
}
Run Code Online (Sandbox Code Playgroud)

component.ts:与选项1中的相同,但ngOnDestroy是不同的:

ngOnDestroy(): void {
    SubscriptionHelper.unsubscribeAll(this.mySubscription);
}
Run Code Online (Sandbox Code Playgroud)

component.spec.ts:

spyOn(SubscriptionHelper, 'unsubscribeAll');
component.ngOnDestroy();
expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledTimes(1);
Run Code Online (Sandbox Code Playgroud)

优点:

  • 我可以测试调用辅助函数

缺点:

  • 我无法测试是否在特定订阅上调用了取消订阅功能.

你们有什么建议?你如何在单元测试中测试清理?

eth*_*far 11

我遇到了完全相同的问题,这是我的解决方案:

组件.ts:

private subscription: Subscription;
//...
ngOnInit(): void {
    this.subscription = this.route.paramMap.subscribe((paramMap: ParamMap) => {
        // ...
    });
}

ngOnDestroy(): void {
    this.subscription.unsubscribe();
}

Run Code Online (Sandbox Code Playgroud)

组件.spec.ts:


let dataMock;

let storeMock: Store;
let storeStub: {
    select: Function,
    dispatch: Function
};

let paramMapMock: ParamMap;
let paramMapSubscription: Subscription;
let paramMapObservable: Observable<ParamMap>;

let activatedRouteMock: ActivatedRoute;
let activatedRouteStub: {
    paramMap: Observable<ParamMap>;
};

beforeEach(async(() => {
    dataMock = { /* some test data */ };

    storeStub = {
        select: (fn: Function) => of((id: string) => dataMock),
        dispatch: jasmine.createSpy('dispatch')
    };

    paramMapMock = {
        keys: [],
        has: jasmine.createSpy('has'),
        get: jasmine.createSpy('get'),
        getAll: jasmine.createSpy('getAll')
    };

    paramMapSubscription = new Subscription();
    paramMapObservable = new Observable<ParamMap>();

    spyOn(paramMapSubscription, 'unsubscribe').and.callThrough();
    spyOn(paramMapObservable, 'subscribe').and.callFake((fn: Function): Subscription => {
        fn(paramMapMock);
        return paramMapSubscription;
    });

    activatedRouteStub = {
        paramMap: paramMapObservable
    };

    TestBed.configureTestingModule({
       // ...
       providers: [
           { provide: Store, useValue: storeStub },
           { provide: ActivatedRoute, useValue: activatedRouteStub }
       ]
    })
    .compileComponents();
}));

// ...

it('unsubscribes when destoryed', () => {
    fixture.detectChanges();

    component.ngOnDestroy();

    expect(paramMapSubscription.unsubscribe).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

这对我有用,我希望它也适用于你!


Ric*_*ira 10

我想出了一个办法让它发挥作用。

首先,我不会为每个订阅创建一个订阅实例,我通常只使用一个订阅实例,并通过使用实现拆解逻辑并允许子订阅的方法“add”添加到所需的多个订阅中。

private subscriptions: Subscription;
 //...
ngOnInit(): void {
  this.mySubscription.add(
    this.store
      .select(mySelector)
      .subscribe((value: any) => console.log(value))
  );
}

ngOnDestroy(): void {
  this.subscriptions.unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

通过这样做,在我的测试中,我可以依赖订阅原型并检测取消订阅方法调用。在这种情况下,在我调用 ngOnDestroy 之后。

const spy = spyOn(Subscription.prototype, 'unsubscribe');
component.ngOnDestroy();
expect(spy).toHaveBeenCalledTimes(1);
Run Code Online (Sandbox Code Playgroud)


Luc*_*lac 6

这对你来说好看吗?

component.mySubscription = new Subscription();

spyOn(component.mySubscription, 'unsubscribe');
component.ngOnDestroy();

expect(component.mySubscription.unsubscribe).toHaveBeenCalledTimes(1);
Run Code Online (Sandbox Code Playgroud)