How do I set a new State to test an observable? - jasmine

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

Related

Cypress using actions from Pinia Vue3

I was learning some cypress from this video: https://www.youtube.com/watch?v=03kG2rdJYtc
I'm interested with he's saying at 29:33: "programatic login"
But he's using vue2 and Vuex.
My project is created with Vite and the state management is Pinia.
So how can I do a programatic login using the pinia action?
For example the welcome logged in user should see dashboard:
describe('Welcome', () => {
it('logged in user should visit dashboard', () => {
// login
cy.visit('/')
cy.url().should('contain', '/dashboard')
})
})
And my userStore:
export const useUserStore = defineStore({
id: 'user',
state: () => ({
username: ref(useLocalStorage('username', null)),
}),
getters: {
isLoggedIn: (state) => state.username !== null,
},
actions: {
login(username, password) {
return useAuthLoginService(username, password)
.then((response) => {
this.username = response.username
})
.catch((error) => {
return Promise.reject(new Error(error))
})
},
},
})
How can I call the login action on the cypress test?
For now as a workaround I'm writing on a localstorage like:
localStorage.setItem('username', 'user')
And it works fine, because userStore catch this item from localstorage and passes like it's logged in... But I don't like this solution, seems fragile, and I'd like to use the action which is made for login users.
Another thing I tried is adding the app variable inside window but it doesn't work for me... don't understand why...
on main.js
The video shows that code:
const vue = new Vue({...})
if(window.Cypress){
window.app = app
}
In my case it's:
const app = createApp(App)
if(window.Cypress){
window.app = app
}
But in cypress tests the window.app it's undefined... I don't know how I would access to userStore using this... like it was vuex.
Using the Pinia demo app as an example:
The store is initialized in App.vue. Add a reference to the newly created store(s) for Cypress to use
export default defineComponent({
components: { Layout, PiniaLogo },
setup() {
const user = useUserStore()
const cart = useCartStore()
if (window.Cypress) {
window.store = {user, cart) // test can see window.store
}
...
In the test
let store;
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`)
.then(win => store = win.store) // get app's store object
})
it('works', () => {
cy.wait(500) // wait for the JS to load
.then(() => store.cart.addItem('Cypress test item')) // invoke action
.then(() => {
const item1 = store.cart.items[0] // invoke getter
cy.wrap(item1)
.should('have.property', 'name', 'Cypress test item') // passes
})
The login action is asynchronous, so return the promise to allow Cypress to wait.
// user.js
async login(user, password) {
const userData = await apiLogin(user, password)
this.$patch({
name: user,
...userData,
})
return userData // this returns a promise which can awaited
},
// main.spec.js
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store before login
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging in
store.user.login('ed', 'ed').then(() => { // wait for API call
cy.wrap(store.user.name).should('eq', 'ed')
})
})
})
Alternatively, wait for the name to change on the page
// main.spec.js
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging on
store.user.login('ed', 'ed')
cy.contains('Hello ed') // waits for name on page to change
.then(() => {
cy.wrap(store.user.name).should('eq', 'ed')
})
})

Angular jasmine how to spy an async spy method

I am new to writing test with async/await in angular.
I have the following code. The service method is an async method. The test fails saying component.options.length is 0.
Can anyone please help me how to fix the error so the options has got the value i set in spy?
Thanks
spec.ts
spySideNavService = jasmine.createSpyObj('SideNavService', [], {
setOrgUserDetails: () => {},
loadMenus: () =>
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[]
});
describe('ngOnInit', () => {
it('should add navigation options', () => {
expect(component.options.length).toBeGreaterThan(0);
});
});
component:
ngOnInit(): void {
this.options = await this.sideNavService.loadMenus();
}
SideNavService:
async loadMenus(): Promise<NavOption[]> {
//logic
}
Tried answer given below but still not working:
describe('ngOnInit', () => {
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
tick();
expect(component.options.length).toBeGreaterThan(0);
}));
});
You need to use fakeAsync/tick to control promises.
// !! add fakeAsync
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
// !! call ngOnInit
component.ngOnInit();
console.log(component.options);
tick();
console.log(component.options);
expect(component.options.length).toBeGreaterThan(0);
}));
Before, the test would go to the await line and go back to the test for the expect because the await is saying to do this later. Now with the tick, we are saying if they are any promises created, resolve them before moving forward.
Also, I think you're missing a Promise.resolve on loadMenus.
loadMenus: () => Promise.resolve(
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[])
I am thinking the Promise.resolve is required so it can be awaited.
edit
I don't think the done callback will help you.
You can try using await fixture.whenStable() to wait for the promise(s). Try this:
describe('ngOnInit', () => {
it('should add navigation options', async () => {
component.ngOnInit();
await fixture.whenStable();
expect(component.options.length).toBeGreaterThan(0);
});
});

mocked method returning undefined

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

angular unit test, method has already been spied upon issue after updating into angular9

I just updated my angular app into version 9. Everything works fine excluding the unit tests. After updating, the unit tests not working.
Below is my sample test suite
describe("test Component", () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
],
providers: [
{ provide: InitService, useValue: mockInit },
{ provide: ControlsService, useValue: genericMock },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
it('should initialize slides', () => {
const fixture = TestBed.createComponent(AppComponent);
const slides = TestBed.inject(SlideChangeService);
const controls = TestBed.inject(ControlsService);
spyOn(slides, 'initialize');
spyOn(controls, 'initialize');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(slides.initialize).toHaveBeenCalled();
expect(controls.initialize).toHaveBeenCalled();
});
});
});
it works fine before the angular updation, but now it shows below error!!!
Error: <spyOn> : initialize has already been spied upon
so, how we can spy on two different services that using same method name in angular9 unit tests?

Testing ngrx Effects with Jasmine spy

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!

Resources