[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
I want to run some integration tests for the ngrx 4 state store in an Angular 5 app. My desire is to test dispatching ngrx actions, effects, webapi, mock server and selectors all together in one shot. Writting separate tests for effects and reducers is going to be a time sink and I need to cut some corners. I only need to know when one of my pages fails not the exact spot.
I managed to do this in a previous project while using redux together with Angular 2. That was quite easy to setup. However, with ngrx I have some trouble. Somehow AccountsWebapi is not properly injected in AccountsEffects class when running the TestBed. Instead of receiving an instance of the webapi, it looks like I'm getting the constructor. At the same time, the same webapi seems to be injected and instantiated properly in _AccountsService.
I expect to inject an instance of the AccountsWebapi in AccountsEffects.
accounts.service.spec.ts
describe("AccountsService - ", () => {
let accService: AccountsService;
beforeAll( ()=> {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({appReducer}),
EffectsModule.forRoot([AccountsEffects]),
],
declarations: [],
providers: [
{ provide: AccountsService, useValue: AccountsService },
{ provide: AccountsWebapi, useValue: AccountsWebapi },
// ... Other dependencies
]
}).compileComponents();
// Instantiation
let _AccountsUtilsService = TestBed.get(AccountsUtilsService);
let _store = TestBed.get(Store);
let _AccountsService = TestBed.get(AccountsService);
// ... Other dependencies
accService = new _AccountsService(
new _AccountsUtilsService(),
new _AccountsWebapi(),
_store,
// ... Other dependencies
);
}));
it("Sample Test", () => {
console.log(`Accounts SERVICE`, accService.accountsWebapi);
accService.getAccountAsync(1);
expect(1).toEqual(1);
});
accounts.effects.ts
@Injectable()
export class AccountsEffects {
constructor(
private actions$: Actions,
private store$: Store<AppState>,
private accountsService: AccountsService,
private accountsWebapi: AccountsWebapi,
) {
console.log('Accounts EFFECTS', this.accountsWebapi)
// Typing this line will instantiate the dependency...
this.accountsWebapi = new (this.accountsWebapi as any)()
}
"dependencies": {
"@angular/animations": "5.1.3",
"@angular/common": "5.1.3",
"@angular/compiler": "5.1.3",
"@angular/core": "5.1.3",
"@angular/forms": "5.1.3",
"@angular/http": "5.1.3",
"@angular/platform-browser": "5.1.3",
"@angular/platform-browser-dynamic": "5.1.3",
"@angular/platform-server": "5.1.3",
"@angular/router": "5.1.3",
"@ngrx/core": "1.2.0",
"@ngrx/effects": "4.1.1",
"@ngrx/store": "4.1.1",
"@ngrx/store-devtools": "4.1.1",
"reflect-metadata": "0.1.10",
"reselect": "2.5.4",
"rxjs": "5.5.6",
"zone.js": "0.8.4"
},
If I executethis.accountsWebapi = new (this.accountsWebapi as any)()I get an instance of the AccountsWebApi but this is not nice at all...
Finally, I found a way to get this kind of test running. I am 100% sure there is a far better way to do it. Until I find it, this has to do.
/** Force initialise the web api in effects */
@Injectable()
class AccountsEffectsTest extends AccountsEffects {
constructor(
protected _actions$: Actions,
protected _store$: Store<AppState>,
protected _accountsWebapi: AccountsWebapi,
) {
super(
_actions$,
_store$,
_accountsWebapi,
);
// This line looks ugly but it spares a lot of imports
// Certainly there is a better way then this
let providers = ((new HttpModule()) as any).__proto__.constructor.decorators['0'].args['0'].providers;
let injector = ReflectiveInjector.resolveAndCreate([
...providers
]);
this.accountsWebapi = new (_accountsWebapi as any)(
null,
injector.get(Http)
);
console.log('Accounts EFFECTS', this.accountsWebapi);
}
}
Finally I can get an instance of the webapi in the effects capable of running real requests. Now all I have to do is to mock a webapi. I know it goes against the grain. I've done my fair share of research on what is the best way to test. I still find it more suitable to run integration tests rather than unit tests for the kind of app I'm building.
Due to some misunderstanding about how to configure the TestBed the AccountsWebapi constructor was delivered instead of the instance. That was because I was using { provider: AccountsWebapi, useValue: AccountsWebapi }. The proper setup is to make use of useClass as demonstrated here { provider: AccountsWebapi, useClass: AccountsWebapi }. When using a class the dependency injection framework will trigger the construction step and it will inject the proper dependencies as longs as they are configured properly.
Another issue was that the AuthHttp service was not provided in AccountsWebapi. This was easily solved by declaring the HttpModule as a dependency in the testBed imports. Also, this was a good opportunity for me to learn that I have several tightly coupled services that need to be provided together in the TestBed. In the future, I need to reduce the number of dependencies between services or loosen them by using events.
After finishing all these modifications the TestBed can be easily instantiated without the need to wrap the EffectsClass in another layer that takes care of initing the AccountWebapi. That was a grossly overengineered solution due to lack of proper understanding of the dependency injection framework.
Glad you got it figured out :+1:
this is my code for effects.spec.ts
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
EffectsModule.forRoot([sampledataeffects]),
],
providers: [AuthService,SampleDataService,provideMockActions(() => actions)]
});
effects = TestBed.get(sampledataeffects);//line 8
// userService = TestBed.get(SampleDataService);
am getting Chrome 70.0.3538 (Windows 10 0.0.0): Executed 0 of 0 ERROR (0.006 secs / 0 secs)
if i remove the sampledataeffects from provider and also comment the line 8 then rest all test case would be executed
but in this condition nothing is going executed .
am not getting any solution for this not even any error in console
Most helpful comment
TestBed can instantiate EffectsModule
Due to some misunderstanding about how to configure the TestBed the
AccountsWebapiconstructor was delivered instead of the instance. That was because I was using{ provider: AccountsWebapi, useValue: AccountsWebapi }. The proper setup is to make use ofuseClassas demonstrated here{ provider: AccountsWebapi, useClass: AccountsWebapi }. When using a class the dependency injection framework will trigger the construction step and it will inject the proper dependencies as longs as they are configured properly.Another issue was that the
AuthHttpservice was not provided inAccountsWebapi. This was easily solved by declaring theHttpModuleas a dependency in the testBed imports. Also, this was a good opportunity for me to learn that I have several tightly coupled services that need to be provided together in the TestBed. In the future, I need to reduce the number of dependencies between services or loosen them by using events.After finishing all these modifications the TestBed can be easily instantiated without the need to wrap the EffectsClass in another layer that takes care of initing the
AccountWebapi. That was a grossly overengineered solution due to lack of proper understanding of the dependency injection framework.