Как выполнить юнит-тест функции отписки в угловых - PullRequest
0 голосов
/ 21 сентября 2018

Я хотел бы найти способ протестировать вызов функции отказа от подписки для подписок и тем.

Я предложил несколько возможных решений, но у каждого из них есть свои плюсы и минусы.Помните, что я не хочу изменять модификатор доступа к переменной для целей тестирования.

  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();
}

component.spec.ts:

spyOn(component['mySubscription'], 'unsubscribe');
component.ngOnDestroy();
expect(component['mySubscription'].unsubscribe).toHaveBeenCalledTimes(1);

плюсы:

  • Я могу связаться с mySubscription.
  • Я могу проверить, что метод отмены подписки был вызван для правильной подписки.

минусы:

  • Я могу достичь своей подписки только с отражением, чего я хотел бы избежать, если это возможно.

Я создаю переменную для подписки, как в варианте 1., но вместо достижения переменной с отражением я просто проверяю, был ли вызван метод отмены подписки, не зная источника.

component.ts: такой же, как в варианте 1


component.spec.ts:

spyOn(Subscription.prototype, 'unsubscribe');
component.ngOnDestroy();
expect(Subscription.prototype.unsubscribe).toHaveBeenCalledTimes(1);

плюсы:

  • Я могу проверить, был ли вызван метод отписки

cons:

  • Не могу проверить источник вызванного метода отписки.

Я реализовал вспомогательную функцию, которая вызывает метод отмены подписки для переданных параметров, которые являются подписками.

subscription.helper.ts:

export class SubscriptionHelper {

    static unsubscribeAll(...subscriptions: Subscription[]) {
        subscriptions.forEach((subscription: Subscription) => {
                subscription.unsubscribe();
            });
    }
}

component.ts: такой же, как в варианте 1, но ngOnDestroy отличается:

ngOnDestroy(): void {
    SubscriptionHelper.unsubscribeAll(this.mySubscription);
}

component.spec.ts:

spyOn(SubscriptionHelper, 'unsubscribeAll');
component.ngOnDestroy();
expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledTimes(1);

плюсы:

  • Я могу проверить, была ли вызвана вспомогательная функция

минусы:

  • Я не могу проверить, что функция отмены подписки была вызвана для определенной подписки.

Что вы, ребята, предлагаете?Как проверить чистоту в модульном тесте?

Ответы [ 5 ]

0 голосов
/ 05 апреля 2019

У меня была точно такая же проблема, вот мое решение:

component.ts:

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

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

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

Это работает для меня, я надеюсь, что это будет и для вас!

0 голосов
/ 29 марта 2019

Вам это нравится?

component.mySubscription = new Subscription();

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

expect(component.mySubscription.unsubscribe).toHaveBeenCalledTimes(1);
0 голосов
/ 21 сентября 2018

Лично я не возражаю против использования первого варианта, когда вы выбираете частное свойство mySubscription.

Но если вы не хотите получать доступ к частным свойствам и используете асинхронный тест, когда вынеобходимо вызвать функцию done самостоятельно, вы можете сделать ее необязательным параметром для ngOnDestroy() (это также предполагает, что вы вызовете ngOnDestroy только один раз за время существования компонентов):

function ngOnDestroy(done?: () => void): void {
  this.mySubscription.add(done);
  this.mySubscription.unsubscribe();
}

Теперь вызывается ngOnDestroy сначала отписывается mySubscription, а затем вызывает done, если установлено.Вы знаете, что подписка была отписана, потому что тест прошел и не прошел по таймауту.

Тогда в ваших тестах вы получите, например:

describe('', () => {
  it('', done => {
    const component = ...
    component.ngOnDestroy(done)
  })
})

Демонстрационная версия без тестов: https://stackblitz.com/edit/rxjs6-demo-rujesw?file=index.ts

0 голосов
/ 21 сентября 2018

Я ухожу от КОММЕНТАРИЙ, чтобы получить больше места, даже если мой - просто открытый аргумент.Я надеюсь, что это приемлемо для SO.

Я хотел бы проверить, что компонент вызывает отмену подписки при каждой подписке

Я думаю, что это интересный случай дизайн теста .

Все мы знаем, что важно не оставлять подписки живыми, когда компонент уничтожен, и поэтому стоит подумать, как это проверить.

В то же время это можно рассматривать как подробность реализации , и это, безусловно, не относится к " общедоступному поведению ", которое компонент SW предоставляет через свойAPI-интерфейсы.И общедоступные API-интерфейсы должны быть в центре внимания тестов, если вы не хотите тесно связывать тесты и код, что препятствует рефакторингу.

Плюс AFAIK RxJS не предоставляет механизм для перечисления всех активных подписок в данный момент, поэтому задача поиска способа проверить это остается на нас.

Итак, если вы хотитечтобы достичь цели тестирования того, что все подписки отписаны, вам нужно принять конкретный дизайн, чтобы разрешить это, например, использовать массив, в котором вы храните все свои подписки, а затем проверить, все ли они закрыты.Это линия вашего SubscriptionHelper.

Но даже это не обязательно ставит вас в безопасное место.Вы всегда можете создать новую подписку и не добавлять ее в массив, возможно, просто потому, что вы забыли сделать это в своем коде.

Таким образом, вы можете проверить это, только если вы придерживаетесь своей дисциплины кодирования , т.е. вы добавляете подписки в массив.Но это равносильно тому, что вы отмените подписку на все подписки методом ngOnDestroy.Даже это дисциплина кодирования.

Итак, имеет ли смысл проводить такой тест, даже если конечная цель теста важна?Разве это не должно быть что-то, что можно получить с помощью обзора кода?

Очень любопытно прочитать мнения

0 голосов
/ 21 сентября 2018

Относительно вашего последнего пункта

Я не могу проверить, была ли вызвана функция отмены подписки для определенной подписки.

Вы можете сделать это на самом деле.

// get your subscrption
const mySubscription = component['mySubscription'];

// use the reference to confirm that it was called on that specific subscription only.
expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledWith(mySubscription);

Теперь, когда дело доходит до тестирования, он должен просто проверять / получать доступ / вызывать открытых участников и тестировать / ожидать их результатов.

Используя роскошь javascript, вы ожидаете / проверяете частных пользователей, даже это хорошо Я думаю.Даже если я это сделаю, я могу ошибаться.Производительная критика и комментарии приветствуются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...