i am working on jasmine unit tests for angular and the mocking is not working. The junit code is:
beforeEach(() => {
menuSpy = spyOn(menuService, 'getMenus');
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should test menu', () => {
spyOn(menuSpy, 'shouldHighlight').and.returnValue(true);
menuSpy.and.returnValue(MENUS);
// component assertions code
});
and the component is as follows:
#Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: [ './menu.component.scss' ]
})
export class MenuComponent {
menus = []
constructor(private menuService: MenuService) {
menus = menuService.getMenus(); // menus is always undefined
}
menus here is always null. I am stuck here all day. Any help is appreciated.
It's because you are mocking your methods after they are already used in the constructor. You should move your mocks before the TestBed.createComponent call. You can try something like:
it('should test menu', () => {
spyOn(menuSpy, 'shouldHighlight').and.returnValue(true);
spyOn(menuService, 'getMenus').and.returnValue(MENUS);
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// Component assertions
});
Related
I have a component that has addTodo method and todos property, when addTodo is invoked I get all the todos from NGXS store and assign them to todos property
export class AppComponent {
#Select("todo.todos") todos$!: Observable<Todo[]>;
addTodo() {
const todo = { title: "Todo title" };
this.store
.dispatch(new TodoActions.AddTodo(todo))
.pipe(withLatestFrom(this.todos$))
.subscribe(([_, todos]) => {
this.todos = todos;
console.log(this.todos); //(1)
});
}
}
Works fine in the browser, Now I want to write a unit test for this, Below is what I have currently
it("should #addTodo", (done) => {
const todo = {title: "Todo title"};
actions.pipe(ofActionCompleted(TodoActions.AddTodo)).subscribe(() => {
console.log(component.todos); //(2)
expect(component.todos).toBe(todo); //Assertion fails
done();
});
component.addTodo();
});
log (2) prints first and gives undefined, then log (1) gets printed which means data is assigned later after the assertion, How to test this scenario?
I have tried all the day to make a simple test in jasmine, but i think i am doing something wrong. I have a piece of code that i wish to test, but i can't go inside. I was trying to follow nrgx 7 documentation, but i failed.
The unit test below should test my enderecoFeatureSubscription. The store.setState({ cep: null, endereco: RES }) is doing nothing with the store, so my subscription doens't do anything
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
let store: MockStore<ICepState>
const initialState = {
cep: null, endereco: null
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [StoreModule.forRoot({}),],
providers: [
provideMockStore({ initialState: CEPSTATE })
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
store = TestBed.get(Store);
});
it('should test enderecoFeatureSubscription ', () => {
store.setState({ cep: null, endereco: RES })
expect(component.endereco).toEqual(RES)
});
Component
private enderecoFeatureSubscription = this.store.pipe(select(enderecoFeatureSelector)).subscribe((endereco: IEndereco | any) => {
if (!endereco) {
return;
}
this.endereco = endereco
})
If you can help i thank you, because i hav wasted a lot of time with it.
In ngrx ver. > 8.0.0, there is a method store.refreshState refreshes the state if you use store.setState on respective overridden selectors. Unfortunately, refreshState method does not exist in ngrx 7. There is an alternative to that - you should override the desired selector using store.overrideSelector like this -
it('should test enderecoFeatureSubscription ', () => {
store.overrideSelector(enderecoFeatureSelector, <put you mocked value>
fixture.detectChanges(); //MAKE sure to remove fixture.detectChanges() from beforeEach
expect(component.endereco).toEqual(RES)
});
i did some changes to my test work fine.
1 - Removed 'const initialState' and imported from my app state file.
2 - The type of MockStore, i changed to my app state type
3 - In the test, i set a new value to 'cepState.endereco' and call setState with initialState
4 - I changed 'store' for 'mockStore', but it doesn't make diference
5 - finally, i brought the right import
Look the code bellow:
describe('FormComponent', () => {
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
let mockStore: MockStore<AppState>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [
StoreModule.forRoot({ 'cepState': CepReducer })
],
providers: [provideMockStore({ initialState })]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
mockStore = TestBed.get(Store);
});
it('should test new endereco state', () => {
initialState.cepState.endereco = RES
mockStore.setState(initialState)
expect(component.endereco).toEqual(RES)
});
});
Looking at the code generated by angular cli, the beforeEach method uses async but doesn't call done(). How does it work then? Isn't calling done mandatory when using async?
beforeEach(async(() => { //async
TestBed.configureTestingModule({
imports: [AppModule,RouterTestingModule.withRoutes(routes), ReactiveFormsModule]
})
.compileComponents();
}));//done not called
beforeEach(() => {
fixture = TestBed.createComponent(PageNotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
The done() is the last callback an async function should call, calling it between will throw an error.
Have a look at this guide https://jasmine.github.io/tutorials/async , it describes about done() and it should be handled.
I am writing an ngrx effect and trying to test it. However, the effect calls a service that calls an API that will require authentication. As a result, I am trying to create a spy in Jasmine to handle returning the data. This is my first time using ngrx effects, so I am really unsure where to put different parts of the code. Nothing I have done is allowing this test to run correctly.
The effect is a very simple one as follows:
#Effect() itemSelected: Observable<Action> = this.d.pessimisticUpdate('ITEM_SELECTED', {
run: (action: ItemSelected) => {
return this.myService.getItemById(action.payload).map((res) => ({
type: 'ITEM_INFO_RETURNED',
payload: res
}));
},
onError: (a: ItemSelected, error) => {
console.error('Error', error);
}
});
constructor(private d: DataPersistence<ItemState>, private myService: MyService) {
// add auth headers here
}
My test is currently written as follows:
describe('ItemEffects', () => {
let actions: Observable<any>;
let effects: ItemEffects;
let myService = jasmine.createSpyObj('MyService', ['getItemById']);
let item1: Item = {id: 1, name: 'Item 1'};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}),
],
providers: [
ItemEffects,
DataPersistence,
provideMockActions(() => actions),
{
provide: MyService,
useValue: myService
}
],
});
effects = TestBed.get(ItemEffects);
});
describe('someEffect', () => {
it('should work', async () => {
myService.getItemById.and.callFake(function (id) {
return items.find((r) => r.id === id);
});
actions = hot('-a-|', { a:{ type:'ITEM_INFO_RETURNED', payload:1}});
expect(effects.itemSelected).toEqual(
{ type: 'ITEM_INFO_RETURNED', payload: { item1 } }
);
});
});
});
This is still attempting to use the production MyService (requiring authentication). If I move the myService override out of the provider and into the actual test,
TestBed.overrideProvider(MyService, { useValue: myService });
I get an error that it cannot read the property "itemSelected" of undefined, which would be when I am calling the effects at the very end of the test.
I am really new to ngrx, as well as to TestBed. Is there somewhere else I should be defining this Jasmine spy? Should I be using something other than createSpyOn for this?
Thanks in advance!
I am writing unit testing for a vuejs 2 application that uses Vuex as a store. I have the following pattern in many of my components:
example component thing.vue:
<template>
<div>
{{ thing.label }}
</div>
</template>
<script>
export default {
name: 'thing',
data() { return { } },
computed: {
thing () {
return this.$store.state.thing;
}
}
}
</script>
Example Store State:
export const state = {
thing: { label: 'test' }
};
Example Unit for Thing.vue:
describe('thing ', () => {
const storeMock = new Vuex.Store( state: { thing: { label: 'test' } } );
it('should pull thing from store', () => {
const Constructor = Vue.extend(thing);
const component new Constructor({ store }).$mount();
expect(component.thing).toEqual({ label: 'test' });
});
});
Example Unit test for Store:
import store from './store';
describe('Vuex store ', () => {
it('should have a thing object', () => {
expect(store.state.thing).toEqual({ label: 'test' });
});
});
There is a huge problem with this pattern. When another developer refractors the store state, they will see the Store test fail, but because the thing unit test is based on a mocked version of the store that test with continue to pass, even though that component will never work. There isn't a good way to know a refactor invalidated a Mock.
So how do people unit test this type of dependence?
One way would be to cheat a little on the unit test and use the real store state, but then it isn't really a unit test. The other way is rely on integration testing to catch the mock - store mismatch, but that feels like it would be painful to debug why the unit tests pass but the integration tests are failing.
What we ended up doing is using the actual store. Because the store state is just an object we figured it was acceptable.
We also use the store getters, actions and mutations as templates for jasmine spyies.
// Vuex needs polyfill
import { polyfill } from 'es6-promise';
polyfill();
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
import test from 'app/components/test.vue';
import module from 'app/store/modules/module';
describe('Spec for Test.vue', () => {
var props;
var state;
var actions;
var mutations;
var getters;
var store;
beforeEach( () => {
jasmine.addMatchers(customMatchers);
props = { };
// Don't change the modules
state = Object.assign({}, module.state);
actions = Object.assign({}, module.actions);
mutations = Object.assign({}, module.mutations);
getters = Object.assign({}, module.getters);
// Add require global actions, mutations, and getters here...
actions.globalActionHere = 'anything'; // this turns into a spy
// Update State with required fields
state.defaults = { id: 1 } // default expected when the component loads
// Replace modules copies with mocks
actions = jasmine.createSpyObj('actions', actions);
mutations = jasmine.createSpyObj('mutations', mutations);
getters = jasmine.createSpyObj('getters', getters);
store = new Vuex.Store( { state: { module: state }, getters, actions, mutations } );
} );
it('should have a name of test', () => {
const Constructor = Vue.extend(thing);
const component new Constructor({ store, props }).$mount();
expect(component.$options.name).toBe('test');
});
});
Note the part
jasmine.createSpyObj('actions', actions);
Jasmine spies will use the module to create spyies for each of the methods, which is very useful.