Как переопределить Provider в Angular 5 только для одного теста?

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

import { MyService } from '../services/myservice.service';
import { MockMyService1 } from '../mocks/mockmyservice1';
import { MockMyService2 } from '../mocks/mockmyservice2';
describe('MyComponent', () => {

    beforeEach(async(() => {
        TestBed.configureTestingModule({
        declarations: [
            MyComponent
        ],
        providers: [
            { provide: MyService, useClass: MockMyService1 }
        ]
        })
        .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MapComponent);
        mapComponent = fixture.componentInstance;
        fixture.detectChanges();
    });

    describe('MyFirstTest', () => {
        it('should test with my first mock', () => {
            /**
             * Test with my first mock
             */
        });
    });

    describe('MySecondTest', () => {
        // Here I would like to change { provide: MyService, useClass: MockMyService1 } to { provide: MyService, useClass: MockMyService2 }

        it('should test with my second mock', () => {
            /**
             * Test with my second mock
             */
        });
    });
});

Я вижу, что функция overrideProvider существует, но мне не удалось использовать ее в мой тест. Когда я использую его в «этом», провайдер не меняется. Мне не удалось найти пример вызова этой функции. Не могли бы вы объяснить мне, как им правильно пользоваться? Или у вас есть другой способ сделать это?


person hbaltz    schedule 15.02.2018    source источник


Ответы (5)


Начиная с angular 6 я заметил, что overrideProvider работает со свойством useValue. Итак, чтобы заставить его работать, попробуйте что-то вроде:

class MockRequestService1 {
  ...
}

class MockRequestService2 {
  ...
}

затем напишите вам TestBed, например:

// example with injected service
TestBed.configureTestingModule({
  // Provide the service-under-test
  providers: [
    SomeService, {
      provide: SomeInjectedService, useValue: {}
    }
  ]
});

И всякий раз, когда вы хотите переопределить провайдера, просто используйте:

TestBed.overrideProvider(SomeInjectedService, {useValue: new MockRequestService1()});
// Inject both the service-to-test and its (spy) dependency
someService = TestBed.get(SomeService);
someInjectedService = TestBed.get(SomeInjectedService);

Либо в функции beforeEach(), либо поместите его в функцию it().

person Dimitrios Vythoulkas    schedule 09.08.2018
comment
{useValue: new MockRequestService1()} был недостающим элементом для меня. Жаль, что он не принимает useClass, как TestBed.configureTestingModule(). - person Jacob Stamm; 12.10.2018
comment
Я пробовал это, но как только вы вызвали compileComponents, overrideProvider больше не работает (даже если вы снова вызовете compileComponents). Поэтому я не думаю, что этот метод будет работать для двух тестовых случаев в одной спецификации. - person Benjamin Caure; 17.10.2018
comment
@BenjaminCaure .... конечно, не работает при попытке переопределить ту же спецификацию. - person Vincent J. Michuki; 11.11.2018
comment
Мне также пришлось создать два разных класса mockApi, а для шутливых модульных тестов я просто сделал еще один testBed.configurationModule, а затем указал, какой фиктивный сервис в разделе поставщиков. - person tony2tones; 28.05.2021

Если служба вводится как общедоступная собственность, например:

@Component(...)
class MyComponent {
  constructor(public myService: MyService)
}

Вы можете сделать что-то вроде:

it('...', () => {
  component.myService = new MockMyService2(...); // Make sure to provide MockMyService2 dependencies in constructor, if it has any.
  fixture.detectChanges();

  // Your test here...
})

Если внедренная служба хранится в частном свойстве, вы можете записать ее как (component as any).myServiceMockMyService2 = new MockMyService2(...);, чтобы обойти TS.

Это некрасиво, но это работает.

Что касается TestBed.overrideProvider, мне не повезло с этим подходом (было бы гораздо лучше, если бы он работал):

it('...', () =>{
  TestBed.overrideProvider(MyService, { useClass: MockMyService2 });
  TestBed.compileComponents();
  fixture = TestBed.createComponent(ConfirmationModalComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();

  // This was still using the original service, not sure what is wrong here.
});
person Filip Voska    schedule 29.03.2018
comment
как только вы вызываете compileComponents, TestBed замораживается, поэтому overrideProvider в этом случае не работает. - person Benjamin Caure; 17.10.2018
comment
Просто удалите вызов compileComponents и получите услугу внутри каждого вызова it() - person Adrian Marinica; 19.09.2019
comment
Я столкнулся с проблемами с этим подходом. Учтите следующее: it('should not dispatch "ShoppingListActions.AddIngredients" when recipe has empty ingredient list', async(() => { component.recipe.ingredients = []; addIngredientsBtn.nativeElement.click(); expect(dispatchSpy).not.toHaveBeenCalled(); })); Тест пройден, но поведение сохраняется для последующих тестов в том же блоке описания. Вызов component.recipe.ingredients = ingredients в конце теста после expect или внутри блока afterEach не сбрасывает список - person Oisín Foley; 16.05.2020

Если вам нужно TestBed.overrideProvider() с разными значениями для разных тестовых случаев, TestBed замораживается после вызова TestBed.compileComponents(), как уже указывал @Benjamin Caure. Я обнаружил, что он также завис после вызова TestBed.get().

В качестве решения в вашем «основном» describe используйте:

let someService: SomeService;

beforeEach(() => {
    TestBed.configureTestingModule({
        providers: [
            {provide: TOKEN, useValue: true}
        ]
    });

    // do NOT initialize someService with TestBed.get(someService) here
}

И в ваших конкретных тестовых случаях используйте

describe(`when TOKEN is true`, () => {

    beforeEach(() => {
        someService = TestBed.get(SomeService);
    });

    it(...)

});

describe(`when TOKEN is false`, () => {

    beforeEach(() => {
        TestBed.overrideProvider(TOKEN, {useValue: false});
        someService = TestBed.get(SomeService);
    });

    it(...)

});

person Katja    schedule 17.01.2020

Просто для справки, если кто-нибудь сталкивается с этой проблемой.

я пытался использовать

TestBed.overrideProvider(MockedService, {useValue: { foo: () => {} } });

это не работало, тем не менее, в тесте был внедрен оригинальный сервис (тот, что с providedIn: root)

В тесте я использовал псевдоним для импорта OtherService:

import { OtherService } from '@core/OtherService'`

в то время как в самой службе у меня был импорт с относительным путем:

import { OtherService } from '../../../OtherService'

После исправления, чтобы test и service имели одинаковый импорт, TestBed.overrideProvider() начал действовать.

Env: библиотека Angular 7 — не приложение и не шутка

person Felix    schedule 13.09.2019
comment
Если у кого-то есть объяснение этому поведению, было бы неплохо объяснить. - person Felix; 24.03.2021

Я столкнулся с аналогичной проблемой, но в более простом сценарии, всего один тест (опишите (...)) с несколькими спецификациями (это (...)).

Решение, которое сработало для меня, заключалось в отсрочке команд TestBed.compileComponents и TestBed.createComponent(MyComponent). Теперь я выполняю их для каждого отдельного теста/спецификации после вызова TestBed.overrideProvider(...) при необходимости.

describe('CategoriesListComponent', () => {
...
beforeEach(async(() => {
  ...//mocks 
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])],
    declarations: [CategoriesListComponent],
    providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}]
  });
}));
...

it('should call SetCategoryFilter when reload is false', () => {
  const mockActivatedRouteOverride = {...}
  TestBed.overrideProvider(ActivatedRoute, {useValue: mockActivatedRouteOverride });
  TestBed.compileComponents();
  fixture = TestBed.createComponent(CategoriesListComponent);

  fixture.detectChanges();

  expect(mockCategoryService.SetCategoryFilter).toHaveBeenCalledTimes(1);
});
person J.J    schedule 12.05.2020
comment
Я думаю, что это не очень хорошая идея, если вы закончите с кучей повторяющегося кода, чтобы настроить почти одно и то же во всех тестах. - person Tawfiek Khalaf; 02.09.2020
comment
Что ж, вопрос в том, как переопределить провайдера только для одного теста... если остальная часть теста будет использовать одну и ту же инициализацию, вы можете извлечь эту общую инициализацию в общий метод и вызвать из остальных модульных тестов. - person J.J; 02.09.2020