How override Provider in Angular 5 for only one test?
Asked Answered
H

6

80

In one of my unit test files, I have to mock several times the same service with different mocks.

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
             */
        });
    });
});

I see that the function overrideProvider exists, but I did not manage to use it in my test. When I use it in a "it", the provider doesn't change. I didn't manage to find an example where this function is called. Could you explain me how to use it properly? Or have you an other method to do that?

Hyetal answered 15/2, 2018 at 15:45 Comment(0)
C
36

As of angular 6 I noticed that overrideProvider works with the useValue property. So in order to make it work try something like:

class MockRequestService1 {
  ...
}

class MockRequestService2 {
  ...
}

then write you TestBed like:

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

And whenever you want to override the provider just use:

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

Either in a beforeEach() function or place it in an it() function.

Conal answered 9/8, 2018 at 9:51 Comment(4)
{useValue: new MockRequestService1()} was the missing piece for me. Shame it doesn't accept useClass like TestBed.configureTestingModule() does.Uprise
I tried this but as soon as you have called compileComponents, overrideProvider does not work anymore (even if you call compileComponents again). So I don't think this method will work for 2 test case in the same specJerome
@BenjaminCaure....sure does not work when trying to override in same spec.Following
I had to also make two different mockApi classes and for the jest unit tests i just made another testBed.configurationModule and then specified which mock service in the providers section.Mccutchen
T
30

If you need TestBed.overrideProvider() with different values for different test cases, TestBed is frozen after call of TestBed.compileComponents() as @Benjamin Caure already pointed out. I found out that it is also frozen after call of TestBed.get().

As a solution in your 'main' describe use:

let someService: SomeService;

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

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

And in your specific test cases use

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(...)

});

Tecla answered 17/1, 2020 at 15:25 Comment(0)
O
15

If the service is injected as public property, e.g.:

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

You can do something like:

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

  // Your test here...
})

If injected service is stored in a private property, you can write it as (component as any).myServiceMockMyService2 = new MockMyService2(...); to bypass TS.

It's not pretty but it works.

As for TestBed.overrideProvider, I had no luck with that approach (which would be much nicer if it worked):

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.
});
Oceanid answered 29/3, 2018 at 12:23 Comment(3)
as soon as you call compileComponents, TestBed is frozen so overrideProvider does not work for this caseJerome
Just delete the compileComponents call and get the service inside each it() callDiabolo
I ran into issues with this approach. Consider the following: it('should not dispatch "ShoppingListActions.AddIngredients" when recipe has empty ingredient list', async(() => { component.recipe.ingredients = []; addIngredientsBtn.nativeElement.click(); expect(dispatchSpy).not.toHaveBeenCalled(); })); Test passes but behaviour persists to subsequent tests in the same describe block. Calling component.recipe.ingredients = ingredients at the end of the test after the expect, or inside an afterEach block doesn't reset the listHurst
G
8

I was facing similar problem, but in a simpler scenario, just one test(describe(...)) with multiple specifications(it(...)).

The solution that worked for me was postponing the TestBed.compileComponents and the TestBed.createComponent(MyComponent) commands. Now I execute those on each individual test/specification, after calling TestBed.overrideProvider(...) when needed.

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);
});
Geophilous answered 12/5, 2020 at 23:24 Comment(2)
I think it is not a good idea you'll end up with a ton of repeated code to configure almost the same thing in all tests.Castara
Well, the question is how to override provider for only one test...if the rest of the test are going to share the same initialization you can extract that commond initialization into a common method and called from the rest of the unit testsGeophilous
F
6

Just for reference, if annynone meets this issue.

I tried to use

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

it was not working, still the original service was injected in test (that with providedIn: root)

In test I used alias to import OtherService:

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

while in the service itself I had import with relative path:

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

After correcting it so both test and service itself had same imports TestBed.overrideProvider() started to take effect.

Env: Angular 7 library - not application and jest

Forewent answered 13/9, 2019 at 7:16 Comment(1)
If anyone has explanation for this behavior it would be nice to explain.Forewent
T
2

I needed to configure MatDialogConfig for two different test scenarios.

As others pointed out, calling compileCompents will not allow you to call overrideProviders. So my solution is to call compileComponents after calling overrideProviders:

  let testConfig;

  beforeEach(waitForAsync((): void => {
    configuredTestingModule = TestBed.configureTestingModule({
      declarations: [MyComponentUnderTest],
      imports: [
        MatDialogModule
      ],
      providers: [
        { provide: MatDialogRef, useValue: {} },
        { provide: MAT_DIALOG_DATA, useValue: { testConfig } }
      ]
    });
  }));

  const buildComponent = (): void => {
    configuredTestingModule.compileComponents(); // <-- compileComponents here
    fixture = TestBed.createComponent(MyComponentUnderTest);
    component = fixture.componentInstance;
    fixture.detectChanges();
  };

  describe('with default mat dialog config', (): void => {
    it('sets the message property in the component to the default', (): void => {
      buildComponent(); // <-- manually call buildComponent helper before each test, giving you more control of when it is called.
      expect(compnent.message).toBe(defaultMessage);
    });
  });

  describe('with custom config', (): void => {
    const customMessage = 'Some custom message';
    beforeEach((): void => {
      testConfig = { customMessage };
      TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: testConfig }); //< -- override here, before compiling
      buildComponent();
    });
    it('sets the message property to the customMessage value within testConfig', (): void => {
      expect(component.message).toBe(customMessage);
    });
  });
Turgent answered 16/5, 2022 at 19:55 Comment(1)
This also seems like the cleanest and simplest approach to me. One other thing to note is that you can nest "describe" blocks. So in a file that I just refactored, I created the "buildComponent" function, wrapped all of the existing describes with another describe with a beforeEach that calls buildComponent. All of the normal tests are now handled without updating each test. Now create another top level describe that does your overrides before calling buildComponent, and that takes care of your custom tests.Satirize

© 2022 - 2024 — McMap. All rights reserved.