I want to create a test with Cypress that has a React component that uses an auth library (#okta/okta-react) with a HOC (withOktaAuth).
My component looks like this:
// Welcome.js
import { withOktaAuth } from '#okta/okta-react'
const Welcome = ({authState}) => {
return <div>{authState.isAuthenticated ? 'stubbed' : 'not working'}</div>
}
export default withOktaAuth(Welcome)
I tried to make a test like so:
// test.js
import * as OktaReact from '#okta/okta-react'
const withOktaAuthStub = Component => {
Component.defaultProps = {
...Component.defaultProps,
authState: {
isAuthenticated: true,
isPending: false
},
authService: {
accessToken: '123'
}
}
return Component
}
describe('Test auth', () => {
before(() => {
cy.stub(OktaReact, 'withOktaAuth').callsFake(withOktaAuthStub)
})
it('Stubs auth', () => {
cy.visit('/welcome')
cy.contains('stubbed')
})
})
When I run the test, the component still does not use the stubbed function. Any help is very much appreciated!
It's been 2 years that this questions was submitted, but for those who still encounters that error, Cypress provides a guide about Okta e2e testing: https://docs.cypress.io/guides/end-to-end-testing/okta-authentication#Adapting-the-back-end
Related
I'm trying to unit test a component where you click a button which should then call store.dispatch('favoritesState/deleteFavorite').
This action then calls an api and does it's thing. I don't want to test the implementation of the vuex store, just that the vuex action is called when you click the button in the component.
The Component looks like this
<template>
<ion-item :id="favorite.key">
<ion-thumbnail class="clickable-item remove-favorite-item" #click="removeFavorite()" slot="end" id="favorite-star-thumbnail">
</ion-thumbnail>
</ion-item>
</template>
import {useStore} from "#/store";
export default defineComponent({
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", props.item.id);
}
return {
removeFavorite,
}
}
});
The jest test
import {store} from "#/store";
test(`${index}) Test remove favorite for : ${mockItemObj.kind}`, async () => {
const wrapper = mount(FavoriteItem, {
propsData: {
favorite: mockItemObj
},
global: {
plugins: [store]
}
});
const spyDispatch = jest.spyOn(store, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
});
I have tried different solutions with the same outcome. Whenever the "trigger('click')" is run it throws this error:
Cannot read properties of undefined (reading 'dispatch') TypeError:
Cannot read properties of undefined (reading 'dispatch')
The project is written in vue3 with typescript using composition API and vuex4
I found a solution to my problem.
This is the solution I ended up with.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
I have a component and a Pinia store which contains a state and some actions. The code works perfectly fine in browser and in E2E (cypress) tests, but fail on unit tests. I'm using vue-testing-utils and vitest.
The store function can be called fine from the unit test when the button is clicked, but if the function is in the mounted or main script, it fails the test
src/components/UsersComponent.vue
<script setup>
import { onMounted } from 'vue'
import { useUsersStore } from '#/stores/users.store'
const usersStore = useUsersStore()
// usersStore.resetStatus() // <- This fails in the unit test
onMounted(() => {
usersStore.resetStatus() // <- This fails in the unit test
})
function changeStatus() {
usersStore.changeStatus() // <- This passes in the unit test
}
</script>
<template>
<div>
<p>Status: {{ usersStore.status }}</p>
<button #click="changeStatus()">Change Status</button>
</div>
</template>
src/stores/users.store.js
import { defineStore } from 'pinia'
import { usersAPI } from '#/gateways'
export const useUsersStore = defineStore({
id: 'users',
persist: true,
state: () => ({
status: 'ready',
}),
getters: {},
actions: {
resetStatus() {
this.status = 'ready'
},
changeStatus() {
this.status = 'loading'
},
},
})
src/components/tests/UsersComponent.spec.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '#vue/test-utils'
import { createTestingPinia } from '#pinia/testing'
import UsersComponent from '#/components/UsersComponent.vue'
import { useUsersStore } from '#/stores/users.store'
const wrapper = mount(UsersComponent, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn() })],
},
})
const usersStore = useUsersStore()
describe('UsersComponent', () => {
it('store function is called', async () => {
// arrange
const spy = vi.spyOn(usersStore, 'resetStatus')
const button = wrapper.find('button')
// act
await button.trigger('click')
// assert
expect(spy).toHaveBeenCalled()
})
})
The unit tests return 2 different error. The first is a console log when the function tries to run in onMounted() and the second is what vitest returns.
stderr | unknown test
[Vue warn]: Unhandled error during execution of mounted hook
at <UsersComponent ref="VTU_COMPONENT" >
at <VTUROOT>
FAIL src/components/__tests__/UsersComponent.spec.js [ src/components/__tests__/UsersComponent.spec.js ]
TypeError: usersStore.resetStatus is not a function
❯ src/components/UsersComponent.vue:16:14
16|
17| <template>
18| <div>
| ^
19| <p>Status: {{ usersStore.status }}</p>
20| <button #click="changeStatus()">Change Status</button>
I know this example is a little basic and doesn't really serve a purpose, but I'm wondering how I can have store functions inside the onMounted() (or similar places) without it breaking all my unit tests.
Maybe this can be useful to you:
describe('UsersComponent', () => {
it('changeStatus function is called', async () => {
const wrapper = mount(UsersComponent, {
mounted: vi.fn(), // With this you mock the onMounted
global: {
plugins: [createTestingPinia({
initialState: { // Initialize the state
users: { status: 'ready' },
}
})]
}
})
// Spy the method you call...
const spy = vi.spyOn(wrapper.vm, 'changeStatus');
wrapper.vm.changeStatus()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
I am having problems while trying to test effects in my Angular 11 project. I use the karma runner. It would seem that when I dispatch an action in my tests, the effects does't seem to respond to it.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let actions$: any;
let effects: SimpleEffects;
let store: MockStore<Store>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
effects = TestBed.inject(SimpleEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
actions$ = of(SimpleActionTypes.UnavailablePrice);
// Subscribe to the effect
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
I have tried many ways to enter the subscribe part of my effect (using marbles hot cold ...), but it doesn't seem to work. I have a failure indicating :
"SimpleEffects should fire with a default price FAILED
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)"
The micro project is hosted here : https://github.com/Ushelajyan/simple-ngrx-testing-effects
Thank you in advance for your help.
You override actions$ within your test (it-block). Unfortunately the TestBed.configureTestingModule() gets executed inside a beforeEach block, which happens right before the it-block gets executed.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let store: MockStore<Store>;
const createEffects = (actions$) => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
return TestBed.inject(SimpleEffects);
};
it('should be created', () => {
const effects = createEffects(of(undefined));
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
const actions$ = of(SimpleActionTypes.UnavailablePrice);
// Create the effect with your given mock
const effects = createEffects(actions$)
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
This should do the trick.
I added a createEffect function that configures your TestBed properly with the given actions$ mock and returns a new instance of SimpleEffects.
This instance can then be used to subscribe to its defined effects.
I am created some components with React with Relay/NextJs, I read the documentation for applied tests: https://relay.dev/docs/en/testing-relay-components, I have been tried to create the test for Fragment Container Tests, with the Mock Payload Generator and #relay_test_operation Directive:
import SearchPageContainer from '../components/SearchPageContainer'
import ReactTestRenderer from 'react-test-renderer'
import { QueryRenderer, graphql } from 'react-relay'
import {
createMockEnvironment,
MockPayloadGenerator,
generateAndCompile
} from 'relay-test-utils'
describe('SearchPageContainer', () => {
let testComponent
let environment
beforeEach(() => {
environment = createMockEnvironment()
const TestRenderer = () => (
<QueryRenderer
environment={environment}
query={graphql`
query TestQuery #relay_test_operation {
view: node(id: "test-id") {
...MyConnectionFragment
}
}
`}
variables={{}}
render={({ error, props }) => {
if (props) {
return <SearchPageContainer view={props} />
} else if (error) {
return error.message
}
return 'Loading...'
}}
/>
)
ReactTestRenderer.act(() => {
testComponent = ReactTestRenderer.create(<TestRenderer />)
})
})
it('should have pending operations in the queue', () => {
expect(environment.mock.getAllOperations().length).toEqual(1)
})
it('should resolve query', () => {
environment.mock.resolveMostRecentOperation(operation =>
MockPayloadGenerator.generate(operation)
)
expect(testComponent).toMatchSnapshot()
})
})
I have this error: Relay Transform Error: You supplied a field named node on type Query, but no such field exists on that type.
My configuration in the .babelrc is this:
{
"presets": [
"next/babel",
],
"plugins": [
["relay", {"compat": true, "schema": "schema/schema.json"}],
]
}
I don't know if I need some extra configuration for this #relay_test_operation directive works,
appreciate your help
There are two main modules that you will enjoy using in your tests:
createMockEnvironment
mockPayloadGenerator
The createMockEnvironment nothing more than an implementation the Relay Environment Interface and it also has an additional mock layer, with methods that allow resolving/reject and control of operations (queries/mutations/subscriptions). It is a special version of Relay Environment with additional API methods for controlling the resolving and rejection operations. Therefore the first thing we should do is tell jest that we don't want the default environment but our environment provided by relay-test-utils.
The mockPayloadGenerator is to improve the process of creating and maintaining the mock data for tested components it can generate dummy data for the selection that you have in your operation.
Check this post with examples: How to test your relay components with relay-test-utils and react-testing-library
Let's say I have a very basic vue-class-component as shown below:
<template>
<div>Nothing of interest here</div>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
import http from './../modules/http';
#Component
export default class Example extends Vue {
user = null;
errors = null;
/**
* #returns {string}
*/
getUserId() {
return this.$route.params.userId;
}
fetchUser() {
this.user = null;
this.errors = null;
http.get('/v1/auth/user/' + this.getUserId())
.then(response => this.user = response.data)
.catch(e => this.errors = e);
}
}
</script>
I want to test the fetchUser() method so I just mock the './../modules/http' dependency and make http.get return a Promise. The problem is that in my assertion I want to check if the URL is being built properly and in order to do so the user ID has to come from an hard-coded variable in the test.
I tried something like this but it doesn't work:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const mockedComponent = ExampleInjector({
'./../modules/http': {
get: () => new Promise(/* some logic */)
},
methods: getUserId: () => 'my_mocked_user_id'
});
Unfortunately it doesn't work and I could not find anything this specific in the Vue docs so the question is, how am I supposed to mock both external dependencies and a class component method?
NOTE: I do not want to mock this.$route.params.userId as the userId could potentially come from somewhere else as well (plus this is just an example). I just want to mock the getUserId method.
Since I specifically asked about how I could mock Vue class component methods with the inject loader here's the complete solution for the question at hand:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const getComponentWithMockedUserId = (mockedUserId, mockedComponent = null, methods = {}) => {
if (!mockedComponent) {
mockedComponent = ExampleInjector();
}
return Vue.component('test', {
extends: mockedComponent,
methods: {
getUserId: () => mockedUserId,
...methods
}
});
};
And then in my test case:
const component = getComponentWithMockedUserId('my_mocked_user_id');
const vm = new Vue(component);
I found this to be very helpful when you need to create a partial mock AND inject some dependencies too.
The easiest way to do this is to extend the component and override the method:
const Foo = Vue.extend({
template: `<div>{{iAm}}</div>`,
created() {
this.whoAmI();
},
methods: {
whoAmI() {
this.iAm = 'I am foo';
}
},
data() {
return {
iAm: ''
}
}
})
Vue.component('bar', {
extends: Foo,
methods: {
whoAmI() {
this.iAm = 'I am bar';
}
}
})
new Vue({
el: '#app'
})
In this example I'm using the extends property to tell Vue that Bar extends Foo and then I'm overriding the whoAmI method, you can see this is action here: https://jsfiddle.net/pxr34tuz/
I use something similar in one of my open source projects, which you can check out here. All I'm doing in that example is switching off the required property for the props to stop Vue throwing console errors at me.