Base class observable(variable) remains undefined in spec file - Angular unit testing - javascript

I have a componentA that inherits(extends) another one i.e componentB. The problem is unit tests of componentA are breaking because an observable defined in componentB remains undefined in spec.ts.
Relevant part of ComponentB(Base class)
this.storeObservable = this.store.select(selectCandidateState);
Accessing this in ComponentA,
this.storeObservable.subscribe((candidateState) => {
this.candidateSkills = candidateState.skills;
this.unsavedChanges = false;
});
Now the unit test of componentA are breaking with this message:
TypeError: Cannot read property 'subscribe' of undefined,
while pointing to this.storeObservable.subscribe
ComponentA
export class ComponentA extends ComponentB implements OnInit, OnDestroy
ngOnInit() {
super.ngOnInit();
this.storeObservable.subscribe((candidateState) => {
this.candidateSkills = candidateState.skills;
this.unsavedChanges = false;
});
The unit tests
let shallow: Shallow<ComponentA>;
beforeEach(() => {
const actions$ = new Observable();
const services = [Store];
const mockedServices = [ConfigCzarServiceMock({ test: 'property' })];
shallow = createShallowComponent(
ComponentA,
ProfileModule,
[componentB],
[],
services,
mockedServices,
);
> All the tests break with above error. TypeError: Cannot read property
> 'subscribe' of undefined,

Related

Testing a component with Jest Error in render: "TypeError: Cannot read property 'template' of null"

I have this component in my codebase,
<template>
<v-dialog v-model="modalVisible" content-class="muc-modal" max-width="350" persistent>
<v-card>
<component :is="component"/>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Watch } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Modal = namespace("Modals");
#Component
export default class AppModal extends Vue {
public component: any = null;
#Modal.State
public modalVisible!: boolean;
#Modal.State
public modalComponent!: string;
#Modal.State
public modalComponentPath!: string|null;
get injectedComponent() {
return this.modalComponent;
}
#Modal.Mutation
public hideModal!: () => void
#Watch('injectedComponent')
onModalComponent(componentName: string) {
if(!componentName) return;
this.component = Vue.component(componentName, () => import(`./${this.modalComponentPath}`));
}
}
</script>
<style scoped lang="scss">
.muc-modal {
width:350px;
max-width:90%;
}
</style>
It is modal component that takes another component, this is run via mutation on the modal store,
import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";
#Module({ namespaced: true })
export default class Modals extends VuexModule {
//#region "State"
public modalVisible: boolean = false;
public modalComponent: string|null = null;
public modalComponentPath: string|null = null;
//#endregion "State"
//#region "Mutations"
#Mutation
public showModal(modalComponent:string, modalComponentPath: string|null = null) {
this.modalVisible = true;
this.modalComponent = modalComponent
this.modalComponentPath = modalComponentPath ? modalComponentPath : modalComponent
}
#Mutation
public hideModal() {
this.modalVisible = false;
this.modalComponent = null;
this.modalComponentPath = null;
}
//#endregion "Mutations"
//#region "Getters"
get getVisibility(): boolean {
return this.modalVisible
}
//#endregion "Getters"
//#region "Actions"
//#endregion "Actions"
}
I am wanting to write some tests that a) test the modal display correctly when showModal() mutation is run, b) that it gets hidden correctly when hideModal() mutation is run.
This is my current test file,
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import Modals from '#/store/modal';
import AppModal from '#/components/AppModal.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('AppModal.vue', () => {
let store: any = null;
//let mutations = modals.mutations
beforeEach(() => {
store = new Vuex.Store({
modules: {
"modals" : Modals
}
})
});
it('shows modal when modalVisible is set to true', () => {
console.log(store);
const wrapper = shallowMount(AppModal, {store, localVue});
// const modal = wrapper.find('.muc-modal')
// console.log(modal);
})
});
running this test I get the following response,
console.error node_modules/vuex/dist/vuex.common.js:916
[vuex] module namespace not found in mapState(): Modals/
console.error node_modules/vuex/dist/vuex.common.js:916
[vuex] module namespace not found in mapState(): Modals/
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "TypeError: Cannot read property 'template' of null"
found in
---> <AppModal>
and i have no clue why, can anyone help shed some light on this for me?
This error -
TypeError: Cannot read property 'template' of null
Is a direct result of this coding error -
<component :is="null" />
Why?
The component tag should never have a null value for the is attribute, even if it's momentary. null isn't a valid component, and Jest correctly finds this error even when you don't see it in the browser.
In your above code, the initial value for the property component (which is probably a bad variable name) is null, so that's where this is coming from. A simple fix would be to set a valid component as an initial value so that it is never null.

Angular 6 Karma/Jasmine Unit test case for dynamically added component

I have a component in which i am dynamically adding another chart component. Here is the code:
appendBarChart(historicalData, key, type) {
// Create component dynamically inside the ng-template
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(this.injectableComponentClass);
const viewContainerRef = this.viewContainer;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
....
componentRef.instance.width = 300;
}
Here is the test case:
it('Should append historical data', inject([LineBarChartComponent], (childComponent: LineBarChartComponent) => {
component.appendBarChart(historicalData, 8, 'group');
fixture.detectChanges();
expect(childComponent.maxValue).toBe(5);
expect(childComponent.lineWithBar).toHaveBeenCalled();
}));
When I execute this test case, I get the following error:
Cannot read property "nativeElement" of undefined.
The child component has the following code:
constructor(private viewContainerRef: ViewContainerRef) {
this.elem = this.viewContainerRef.element.nativeElement;
}
Any ideas?

How to test angular guards/resolvers without mocking ActivatedRouteSnapshot

I'm trying to test an Angular Resolver which accesses children routes param.
My guard works fine but I cannot create an unit test easily because I cannot create an ActivatedRouteSnapshot with children routes (read only property).
My resolver
#Injectable({
providedIn: 'root'
})
export class MyResolverGuard implements Resolve<string> {
constructor() {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): string {
return route.firstChild.paramMap.get('my-param');
}
}
My test :
it('should resolve chilren route params', () => {
guard = TestBed.get(MyResolverGuard);
const route = new ActivatedRouteSnapshot();
// Cannot assign children because it's a read only property
route.children = [...];
const myResolverParams = guard.resolve(route, null);
});
Is there other any other ways than using mock ActivatedRouteSnapshot ?
Does my approach to test guard is good ?
Thanks for sharing your strategy.

Passing props to root instances in Vue.js

The problem
I am trying to pass a prop to my root constructor. I understand that propsData is the way to go:
var appComponent = Vue.component('app', require('./components/app/app.vue.html'));
new Vue({
el: '#app-root',
router: new VueRouter({ mode: 'history', routes: routes }),
propsData: { test: 'hi!' },
components: { appComponent },
render: h => h('app')
});
This is the AppComponent that receives the prop:
export default class AppComponent extends Vue {
#Prop()
test: string;
mounted() {
console.log('test = ' + this.test);
// result: test = undefined
// expected: test = 'hi!'
}
}
Things I have tried
The only way to make it work in development (not in production) was:
test: string;
beforeCreate() {
this.test = this.$root.test;
}
It work on development (windows), but in deployment (linux) I got this typescript error:
ERROR in [at-loader] ClientApp/components/app/app.ts:14:34
TS2339: Property 'test' does not exist on type 'Vue'.
Here is another post talking about this.
You can pass props in the data object which is the 2nd argument the createElement alias h function takes in the render option
render: h => h('app', { props: { test: 'hi!' }})

NGRX | store.select(state => state.MyObject) | returns undefined

Situation, I'm trying to retrieve state info using the following approach:
listObject: Array<ListObject>;
this.list$ = this.store.select(state => state.MyListObject);
this.listSub$ = this.list$.subscribe(list => this.listObject = list);
This works for every component except a new component I created a couple days ago. The listSub$ subscription returns an undefined object. The kicker is I can see the state with my Redux DevTools. In state there are two objects in the MyListObject. At a login event, the store is dispatched to load the list; I can see the list load in Redux DevTools as well as logging statements.
I cannot figure out why this.store.select(state => state.MyListObject) returns undefined when state is present. Has anyone seen this issue before?
I made sure that StoreModule.forRoot({}) has my reducer referenced, and the effect I'm using is in the EffectsModule.forRoot([]).
The Problem Code
clatschList: ClatschList;
this.clatschList$ = this.store.select(state => state.ClatschList);
this.ClatschListSub = this.clatschList$.subscribe(list => this.clatschList = list);
MyListObject
export class ClatschList {
public clatsches: Array<ClatschSummary> = [];
public links: Array<Link> = [];
}
App State
export interface ClatschesAppState {
ClatschList: ClatschList;
}
Action
export type Action
= LoadMemberClatsches
| LoadMemberClatschesSuccess;
export const LOAD_MEMBER_CLATCHES = 'LOAD_MEMBER_CLATCHES';
export class LoadMemberClatsches {
readonly type = LOAD_MEMBER_CLATCHES;
constructor() {}
}
export const LOAD_MEMBER_CLATCHES_SUCCESS = 'LOAD_MEMBER_CLATCHES_SUCCESS';
export class LoadMemberClatschesSuccess {
readonly type = LOAD_MEMBER_CLATCHES_SUCCESS;
constructor(public clatschList: ClatschList) {}
}
Reducer
export function MemberClatschesReducer(state: ClatschList = new
ClatschList(), action: ClatschAction.Action) {
switch (action.type) {
case ClatschAction.LOAD_MEMBER_CLATCHES_SUCCESS: {
return action.clatschList;
}
default: {
return state;
}
}
}
Effect
#Effect() LoadMemberClatschList$ = this.actions$
.ofType(ClatschAction.LOAD_MEMBER_CLATCHES).pipe(
switchMap((action: LoadMemberClatsches) =>
this.clatschService.FindAllByMember().pipe(
map(list => new ClatschAction.LoadMemberClatschesSuccess(list)))
)
);
Module
#NgModule({
imports: [
CommonModule,
HttpClientModule,
AuthorizationModule,
StoreModule.forRoot(
{ makerContext: makerContextReducer,
makers: makerReducer,
locations: locationsReducer,
locationSearchResult: locationSearchResultReducer,
locationSearchResultDetail: locationSearchResultDetailReducer,
events: MakerEventsReducer,
eventContext: EventContextReducer,
EventList: EventListReducer,
EventListPagination: EventListPaginationReducer,
SelectedEvent: SelectedEventReducer,
MemberClatschList: MemberClatschesReducer,
}),
EffectsModule.forRoot( [MakerEffect, LocationEffect, EventEffect,
ClatschEffect]),
StoreDevtoolsModule.instrument()
],
declarations: [LocationSearchComponent],
providers: [ MakerService,
StorageService,
EventService,
LocationService,
ClatschRepositoryService,
ClatschService,
ApiResourceService,
LoggingService,
UserNotificationService,
HttpErrorHandlerService ]
})
export class CoreModule { }
Ok thanks for posting the rest of the code. I found your problem. In the module, you declared:
StoreModule.forRoot({
...
MemberClatschList: MemberClatschesReducer,
})
That means your app state will have a property MemberClatschList which will be handled by the MemberClatschesReducer, but both in the AppState interface and your component you called the property ClatschList. It always returns undefined because you're trying to access state.ClatschList, when in reality it should be state.MemberClatschList

Categories