Closed gituser7878-Ultralinq closed 7 years ago
Last provider for a token wins. You can keep adding providers — even for the same token — as long as you haven't frozen the TestingModule by calling compileComponents
or createComponent
.
beforeEach( () => {
TestBed.configureTestingModule({
providers: [ { provide: URLLoaderSvc, useClass: Mock1 } ]
});
});
...
beforeEach( () => {
TestBed.configureTestingModule({
providers: [ { provide: URLLoaderSvc, useClass: Mock2} ]
});
});
...
beforeEach( () => {
TestBed.configureTestingModule({
providers: [ { provide: URLLoaderSvc, useClass: Mock3} ]
});
});
...
it( 'some test', inject([URLLoaderSvc, (loader:URLLoaderSvc) => {
... // loader will be Mock3
}]);
This is useful information, but what to do if you actually called compileComponents
? Is there a way to override a provider after that call ?
Although the inject
example above does the trick, it adds a lot of cruft to the it
declarations. Looking at the Angular docs, I found the TestBed
has some .overrideX()
methods (.overrideModule
, .overrideComponent
, etc...). One of these is .overrideProvider
. To simplify this a bit (and make it feel a bit more like working in mocha/chai/sinon), I configured my .component.spec.ts
like-a-so:
discribe('HeroesComponent', () => {
let component: HeroesComponent;
let fixture: ComponentFixture<HeroesComponent>;
let mockHeroesService: MockHeroesService;
beforeEach(async(() => {
mockHeroesService = new MockHeroesService();
TestBed.overrideProvider(HeroesService, { useValue: mockHeroesService });
TestBed.configureTestingModule({
declarations: [ HeroesComponent ],
providers: [ HeroesService ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HeroesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should call the heroesService\'s getHeroes method', () => {
// Arrange
spyOn(mockHeroesService, 'getHeroes').and.returnValue([]);
// Act
component.getHeroes();
// Assert
expect(mockHeroesService.getHeroes).toHaveBeenCalledTimes(1);
});
});
class MockHeroesService extends HeroesService {}
I like this approach a bit more than the inject example above, just feels cleaner and less confusing.
Another thing to note is that TestBed overrides need to happen before .createComponent
is called. I tried the .overrideProvider
call after .compileComponents
and before .createComponent
in the 2nd beforeEach, and it still worked fine.
If, for whatever reason, the .overrideProvider
needs to be called within a test (after .createComponent
)(@trichetriche), all the setup in the 2nd beforeEach would need to get called again afterwards, but I don't see why that would be needed when the mock service can easily have spies and return values stubbed after the override.
I used the above example based on what the angular-cli generates from the command ng generate component Heroes
for the spec file (v1.2.1). I'm not sure if this is the preferred method of injecting mocks over the inject
method provided above.
@nwayve would like to thank you for sample above code. That cleared how spy works with mockclass together.
If you're trying to do this with a test suite that was scaffolded by the Angular CLI, you can do something like this without needing to change the entire structure of the test:
At the top of the suite, defined a TestBed configuration that will serve as the "base" for your configs:
// Testbed configs
const tb_base = {
declarations: [ MyComponent ],
providers: [
{
provide: ConfigurationService,
useValue: instance(ConfigurationServiceMock)
}
],
imports:[]
}
For your simple tests (that dont need overriding), you just pass this base object into the configureTestingModule()
call:
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule(tb_base)
.compileComponents();
}));
......
3. For any tests that need a new instance of the provider, just override it before you call `compileComponents();`:
```typescript
describe('MyComponent With Override', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule(tb_base);
TestBed.overrideProvider(ConfigurationService, {useValue: instance(ConfigurationServiceMock2)});
TestBed.compileComponents();
}));
......
You can apply the same concept to individual test cases:
describe('MyComponent Single Overrides', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule(tb_base);
}));
it('should override the provider in this one test case', () => {
TestBed.overrideProvider(ConfigurationService, {useValue: instance(ConfigurationServiceMock2)});
TestBed.compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// Assert here
});
.......
This just saved me... thank you...
Is any of this info relevant for angular 6+ ?
I tried the first two approaches but I get an error that I'm unable to configure a testing module once it's been configured.
I looked at some of the Angular Material specs and found out that they make a createComponent
function that they call in beforeEach()
inside nested describe()
, thus you can configure different providers according to different testing scenarios.
Then I ended up with something similar to what nwayve presented: exposing the mocks to the describe block.
describe('SomeComponent', () => {
let fixture;
let component;
// This is the createComponent function
function createComponent(providers: Provider[] = []) {
TestBed.configureTestingModule({
imports: [ /* ... */ ],
declarations: [ /* ... */ ],
providers: [
// Here I set default providers mocks. Providers for the same service
// added in the providers array will override the defaults.
// By default I won't touch MatDialog so I don't need more than a object.
{ provide: MatDialog, useValue: { } },
...providers,
]
})
.compileComponents();
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}
describe('nested describe', () => {
// Inside this block I'll need to mock MatDialog.
let matDialogMock;
beforeEach(() => {
// I prepare the mock object
matDialogMock = {open: () => ({afterClosed: () => of(true)})};
// And here I create the component passing the new mock.
// Note that I use useFactory, because for some reason, useValue
// creates a copy of the object, so I won't be able to create a spy
// using the variable declared at the top of this nested describe.
createComponent([
{ provide: MatDialog, useFactory: () => matDialogMock }
]);
});
it('should work with the new mock', () => {
// Now I create a spy...
const spy = spyOn(matDialogMock, 'open').and.callThrough();
// Then do some stuff, and test against it
expect(spy).toHaveBeenCalled();
});
});
});
Is any of this info relevant for angular 6+ ?
I tried the first two approaches but I get an error that I'm unable to configure a testing module once it's been configured.
@crhistianramirez to get this working with Angular 7.1, call TestBed.resetTestingModule();
before re-configuring it.
Can't guarantee this works with Angular 6 though, haven't tried.
Where are you people getting the instance
function from? Please explain. None of your code works.
@rocketkittens I bet that the instance
functions comes from ts-mockito
: https://github.com/NagRock/ts-mockito.
I was able to substitute the instance method by calling a constructor "useValue: new MockSrv()"
I have:
beforeEach(() => { TestBed.configureTestingModule({ providers: [ DocumentsLoaderSvc, { provide: MgrSvc, useClass: MockMgrSvc }, { provide: URLLoaderSvc, useClass: MockURLLoaderSvcWhenData} ] }); });
How do I override URLLoaderSvc with another mock, in an "it" case with its own unique requirement? Is there something like TestBed.overrideProvider... Right now, I have each it statement in its own "describe", with its own beforeEach.