Что я узнал о насмешках с помощью тестов Spectator
Я упомянул в своем предыдущем сообщении в блоге, что Spectator в настоящее время является моим основным инструментом для тестирования моих приложений Angular. За последние пару недель я узнал кое-что о насмешках с помощью Spectator, и надеюсь, вы найдете их полезными.
Обратите внимание, что я использую Spectator с Jest.
Удалить неиспользуемых провайдеров
В моем createServiceFactory
или createComponentFactory
я объявил, что мои зависимости будут автоматически имитироваться, но я все еще сохранил провайдеров с useValue: {}
. В приведенном ниже примере мой UserApiService
уже автоматически имитируется. В этом случае уже не нужно объявлять UserApiService
в провайдерах с useValue: {}
:
let spectator: SpectatorService<UserService>;
const createService = createServiceFactory({
service: UserService,
providers: [
{ provide: UserApiService, useValue: {}}, // TODO: Remove, we don't need it.
],
mocks: [UserApiService], // Automatically mock
});
beforeEach(() => (spectator = createService()));
Нам не нужны UserApiService
в провайдерах:
let spectator: SpectatorService<UserService>;
const createService = createServiceFactory({
service: UserService,
mocks: [UserApiService],
});
beforeEach(() => (spectator = createService()));
Хранение UserApiService
в провайдерах не имеет побочных эффектов. Однако лучше удалить его, если он не используется.
andReturn() против mockReturnValue()
Я использовал andReturn()
исключительно для имитации методов в тестовом примере с тех пор, как начал использовать Spectator. Я только недавно узнал о Jest’s mockReturnValue()
. Он работает так же, как andReturn()
. Основное отличие, которое я заметил, заключается в том, что andReturn()
не является строгим с типом возвращаемого значения.
Учитывая метод, возвращающий тип Observable<User>
.
class UserService {
getUser(): Observable<User> {
// code here..
}
}
Где User
:
interface User {
firstName: string;
lastName: string;
}
Используя mockReturnValue
, я получу ошибку, если не смоюсь, используя ожидаемый тип возвращаемого значения:
const userUservice = spectator.inject(UserService);
userService.getUser.mockReturnValue(of('user mock'));
Ошибка:
error TS2345: Argument of type 'Observable<string>'
is not assignable to parameter of type 'Observable<User>'.
mockReturnValue()
принимает только объявленный тип возвращаемого значения метода:
const userUservice = spectator.inject(UserService);
userService.getUser.mockReturnValue(of({firstName: 'First', lastName: 'Last'} as User));
Используя andReturn()
, я могу использовать другой тип:
const userUservice = spectator.inject(UserService);
userService.getUser.andReturn(of('user mock')); // I can mock with a string type!
Насмешка по прямому назначению
Я использовал andReturn()
(и вскоре mockReturnValue()
) в тестовых примерах, если меня волнует возвращаемое значение макета. Мне легче заметить, когда я ищу моки, которые происходят в одном тесте.
Тем не менее, есть тестовые случаи, когда меня интересует только утверждение toHaveBeenCalled()
для фиктивного метода. В этом случае я обычно присваивал бы jest.fn()
методу, который я хочу утвердить. Это работает, только если свойство или метод не доступны только для чтения.
Если я использую UserService
в своем компоненте.
constructor(private userService: UserService) {}
Я могу имитировать вызов его метода getUser()
в тесте, напрямую назначив фиктивную функцию. Затем подтвердите с помощью toHaveBeenCalled()
:
spectator.component['userService'].getUser = jest.fn();
//.. some code here
expect(spectator.component['userService'].getUser).toHaveBeenCalled();
Имитация свойств только для чтения
Что, если метод, который я хочу имитировать, доступен только для чтения?
У меня есть геттер в моей службе:
export class UserService {
//.. some code
get canAccess$(): Observable<boolean> {
// .. implementaion
}
}
У меня есть компонент, который использует указанный выше геттер UserService.canAccess$
. Когда я пытаюсь издеваться над этим геттером, используя andReturn()
:
const userService = spectator.inject(UserService);
userService.canAccess$.andReturnvalue(of(false));
Я получаю следующую ошибку:
TS2339: Property 'andReturn' does not exist on type 'Observable '.
Я также не могу назначить макет напрямую, потому что здесь canAccess$
доступен только для чтения:
spectator.component['userService'].canAccess$ = of(false);
Cannot assign to 'canAccess$' because it is a read-only property.
Есть несколько способов справиться с этим.
Использование Object.defineProperty()
Ранее я использовал Object.defineProperty
для изменения свойства объекта службы:
Object.defineProperty(spectator.component['userService'], 'canAccess$', { value: of(true) });
Это сработало. Я подумал, что должен быть способ добиться этого, используя Spectator, а не напрямую изменяя объект службы, см. следующий раздел.
Установка useValue в тестовом примере
Я узнал об этом подходе, когда просматривал примеры в README Spectator.
Я могу объявить макет по умолчанию для canAccess$
в моем вызове createComponentFactory
, установив useValue
.
const createComponent = createComponentFactory({
component: MyComponent,
//...typeOrOptions here
providers: [
{ provide: UserService, useValue: { canAccess$: of(true) } }
],
});
Все тесты в наборе будут использовать это значение по умолчанию canAccess$
, если я не переопределю его в тесте или другом наборе тестов. Чтобы переопределить макет по умолчанию, я могу указать провайдера в тестовом примере с помощью useValue
. В этом примере я изменяю возвращаемое значение на of(false)
.
const provider = {
provide: UserService,
useValue: { canAccess: of(false) },
};
Затем вызовите createComponent()
, используя провайдера с фиктивным переопределением, которое я только что объявил.
spectator = createComponent({
providers: [provider],
});
Я могу переопределить макет по умолчанию в отдельных тестовых примерах.
it('should prevent access...', () => {
const provider = {
provide: UserService,
useValue: { canAccess$: of(false) },
};
spectator = createComponent({
providers: [provider],
});
//.. code here
});
Или я могу объявить переопределение в beforeEach()
, если я хочу использовать его в наборе тестов:
describe('Prevent access', () => {
beforeEach(() => {
const provider = {
provide: UserService,
useValue: { canAccess$: of(false) },
};
spectator = createComponent({
providers: [provider],
});
//.. code here
});
it('should prevent access...', () => {
// .. code here
});
// .. more tests
});
Если вам нравится эта история, вам могут понравиться и другие мои истории о Spectator Test и Angular: