Как издеваться над экспортированной функцией машинописного текста в тесте на жасмин?

Я пытаюсь имитировать функцию, экспортированную из файла машинописного текста, в тесте Jasmine. Я ожидаю, что следующее будет издеваться над импортированным foo и вернуть значение 1 в спецификации для бара.

Похоже, что макет не вызван, поэтому я явно что-то упускаю. Как исправить этот пример?

demo.ts:

export function foo(input: any): any {
  return 2;
}

export function bar(input: any): any {
  return foo(input) + 2;
}

demo.ts.spec:

import * as demo from './demo';

describe('foo:', () => {
  it('returns 2', () => {
    const actual = demo.foo(1);
    expect(actual).toEqual(2);
  });
});

describe('bar:', () => {
  // let fooSpy;
  beforeEach(() => {
    spyOn(demo, 'foo' as any).and.returnValue(1); // 'as any' prevents compiler warning
  });

  it('verifies that foo was called', () => {
    const actual = demo.bar(1);
    expect(actual).toEqual(3); // mocked 1 + actual 2
    expect(demo.foo).toHaveBeenCalled();
  });
});

Неудачи:

  • Ожидается, что 4 равно 3.
  • Ожидается, что будет вызван шпион foo.

person Sevenless    schedule 28.03.2018    source источник


Ответы (2)


Ответ Джеффри помог мне встать на правильный путь.

Чтобы прикрепить шпион к правой ссылке для foo, код продукта должен быть немного изменен. foo() следует называть this.foo()

Приведенный ниже шаблон работает для тестирования (и намного чище, чем запутанная работа, которую я использовал ранее).

demo.ts:

export function foo(input: any): any {
  return 2;
}

export function bar(input: any): any {
  return this.foo(input) + 2;
}

demo.ts.spec:

import * as demo from './demo';

describe('foo:', () => {
  it('returns 2', () => {
    const actual = demo.foo(1);
    expect(actual).toEqual(2);
  });
});

describe('bar:', () => {
  // let fooSpy;
  beforeEach(() => {
    spyOn(demo, 'foo' as any).and.returnValue(1);
  });

  it('verifies that foo was called', () => {
    const actual = demo.bar(1);
    expect(actual).toEqual(3);
    expect(demo.foo).toHaveBeenCalled();
  });
});
person Sevenless    schedule 29.03.2018
comment
Это актуально только для классов. необработанная функция не имеет this. Ну вроде. Машинописец жалуется на это. - person Samuel Thompson; 10.04.2018
comment
Соглашаться. Это типичный вариант использования, когда вам понадобится экземпляр объекта, который использует стратегию для каждый модуль, который вы хотите высмеивать независимо. - person JJWesterkamp; 10.04.2018
comment
Это спасло мне жизнь, спасибо! Кстати, как вы избавились от того, что this неявно имеет тип any, потому что у него нет аннотации типа? NVM, я так понял, это была опция noImplicitThis, а не noImplicitAny - person Hansen W; 30.08.2019

Из этой проблемы на Github: как вы собираетесь использовать функцию слежки за реальная реализация?

Ваша bar реализация вызывает фактическую реализацию foo, потому что она имеет прямую ссылку на нее. При импорте в другой модуль создается новый объект с новыми ссылками:

// This creates a new object { foo: ..., bar: ... }
import * as demo from './demo';

Эти ссылки существуют только в модуле импорта. Когда вы вызываете spyOn(demo, 'foo'), используется именно эта ссылка. Возможно, вы захотите попробовать это в своей спецификации, скорее всего, тест пройден:

demo.foo();
expect(demo.foo).toHaveBeenCalled();

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

person JJWesterkamp    schedule 28.03.2018
comment
Спасибо! Это привело меня к тому, что мне нужно для решения моей проблемы. - person Sevenless; 29.03.2018