【问题标题】:How override Provider in Angular 5 for only one test?如何仅针对一项测试覆盖 Angular 5 中的 Provider?
【发布时间】:2022-01-19 22:07:09
【问题描述】:

在我的一个单元测试文件中,我必须用不同的模拟模拟多次相同的服务。

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 存在,但我没有设法在我的测试中使用它。当我在“它”中使用它时,提供者不会改变。我没有找到调用此函数的示例。你能解释一下如何正确使用它吗?或者你有其他方法吗?

【问题讨论】:

    标签: testing mocking angular5 angular-providers testbed


    【解决方案1】:

    从 Angular 6 开始,我注意到 overrideProvideruseValue 属性一起使用。因此,为了使其正常工作,请尝试以下操作:

    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() 函数中。

    【讨论】:

    • {useValue: new MockRequestService1()} 对我来说是缺失的部分。可惜它不像TestBed.configureTestingModule() 那样接受useClass
    • 我试过了,但是一旦你调用了 compileComponents,overrideProvider 就不再起作用(即使你再次调用 compileComponents)。所以我认为这种方法不适用于同一规范中的 2 个测试用例
    • @BenjaminCaure....在同一规范中尝试覆盖时肯定不起作用。
    • 我还必须制作两个不同的 mockApi 类,为了开玩笑的单元测试,我只是制作了另一个 testBed.configurationModule,然后在提供程序部分指定了哪个模拟服务。
    【解决方案2】:

    如果您需要 TestBed.overrideProvider() 为不同的测试用例提供不同的值,则在调用 TestBed.compileComponents() 后,TestBed 将被冻结,正如 @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(...)
    
    });
    
    

    【讨论】:

      【解决方案3】:

      如果服务是作为公共属性注入的,例如:

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

      【讨论】:

      • 一旦你调用 compileComponents,TestBed 就会被冻结,因此 overrideProvider 不适用于这种情况
      • 只需删除compileComponents 调用并获取每个it() 调用中的服务
      • 我遇到了这种方法的问题。考虑以下情况:it('should not dispatch "ShoppingListActions.AddIngredients" when recipe has empty ingredient list', async(() => { component.recipe.ingredients = []; addIngredientsBtn.nativeElement.click(); expect(dispatchSpy).not.toHaveBeenCalled(); })); 测试通过,但行为会持续到同一描述块中的后续测试。在 expect 之后的测试结束时或在 afterEach 块内调用 component.recipe.ingredients = ingredients 不会重置列表
      【解决方案4】:

      我遇到了类似的问题,但在一个更简单的场景中,只有一个测试(describe(...)) 具有多个规范(it(...))。

      对我有用的解决方案是推迟 TestBed.compileComponentsTestBed.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);
      });
      

      【讨论】:

      • 我认为在所有测试中使用大量重复代码来配置几乎相同的东西并不是一个好主意。
      • 好吧,问题是如何只为一个测试覆盖提供者...如果测试的其余部分要共享相同的初始化,您可以将该通用初始化提取到一个通用方法中并从其余的单元测试
      【解决方案5】:

      仅供参考,如果annynone遇到这个问题。

      我尝试使用

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

      它不工作,仍然在测试中注入原始服务(providedIn: root

      在测试中我使用 aliasimport OtherService:

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

      在服务本身中,我有 import相对路径

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

      在更正后,testservice 本身都有相同的导入 TestBed.overrideProvider() 开始生效。

      Env:Angular 7 库 - 不是应用程序和玩笑

      【讨论】:

      • 如果有人对这种行为有解释,那就太好了。
      猜你喜欢
      • 2022-10-20
      • 2022-01-05
      • 2012-11-11
      • 2011-01-05
      • 1970-01-01
      • 1970-01-01
      • 2018-08-25
      • 2021-10-20
      • 2021-07-09
      相关资源
      最近更新 更多