Jest + Vue Test Utils: Cannot read properties of undefined (reading 'component') - asynchronous

I'm pretty new to testing UI elements and I'm at a loss here. I have a component, ProjectDashboardView, that fetches some information from an API on mount and renders a series of ProjectDashboardCard components with that fetched information. I'm mocking the API call on the tests, using flush-promises to wait for all promises to be resolved before assertion. However, I'm getting this error:
● projectDashboardView › displays ProjectDashboardCards corresponding to all the repositories fetched
TypeError: Cannot read properties of undefined (reading 'component')
14 | describe('projectDashboardView', () => {
15 | it('displays ProjectDashboardCards corresponding to all the repositories fetched', async () => {
> 16 | const wrapper = await mount(ProjectDashboardView, {
| ^
17 | router,
18 | localVue,
19 | mocks: {
at remapInternalIcon (node_modules/vuetify/dist/webpack:/Vuetify/src/util/helpers.ts:233:39)
at VueComponent.getIcon (node_modules/vuetify/dist/webpack:/Vuetify/src/components/VIcon/VIcon.ts:72:31)
at Proxy.render (node_modules/vuetify/dist/webpack:/Vuetify/src/components/VIcon/VIcon.ts:217:23)
at VueComponent.Vue._render (node_modules/vue/dist/vue.runtime.common.dev.js:3559:22)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:21)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3131:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:6002:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5949:9)
at createChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6077:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5978:9)
at createChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6077:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5978:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6499:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3948:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3131:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:6002:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5949:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6499:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3948:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3131:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:6002:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5949:9)
at createChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6077:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5978:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6499:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3948:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3131:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:6002:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5949:9)
at createChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6077:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5978:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6499:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3948:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3131:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:6002:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5949:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6499:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3948:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4069:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4481:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4470:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4076:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8436:10)
at mount (node_modules/#vue/test-utils/dist/vue-test-utils.js:14057:21)
Here's the code for both the test and the component:
ProjectDashboardView.spec.js
import { mount, createLocalVue } from '#vue/test-utils'
import { SERVICES } from '#/utils/common'
import VueRouter from 'vue-router'
import router from '#/router'
import flushPromises from 'flush-promises'
import ProjectDashboardView from '#/components/ProjectDashboard/ProjectDashboardView.vue'
import ProjectDashboardCard from '#/components/ProjectDashboard/ProjectDashboardCard.vue'
jest.mock('#/services/finderService')
const localVue = createLocalVue()
localVue.use(VueRouter)
describe('projectDashboardView', () => {
it('displays ProjectDashboardCards corresponding to all the repositories fetched', async () => {
const wrapper = mount(ProjectDashboardView, {
router,
localVue,
mocks: {
$store: {
state: {
user: {
name: '',
// Array of IDs
repositories: [1, 2],
// Services the user is using to test code quality in their projects
services: SERVICES,
},
},
},
},
})
await flushPromises()
const children = wrapper.findAllComponents(ProjectDashboardCard)
expect(children.length).toBeGreaterThan(0)
})
})
ProjectDashboardView.js
<template>
<v-container class="grid">
<project-dashboard-card
v-for="(value, name) in latestReports"
:key="name"
:repository="value"
/>
<the-add-project-button />
</v-container>
</template>
<script>
import finderService from '#/services/finderService'
import ProjectDashboardCard from '#/components/ProjectDashboard/ProjectDashboardCard'
import TheAddProjectButton from '#/components/ProjectDashboard/TheAddProjectButton'
import { mapState } from 'vuex'
export default {
name: 'ProjectDashboardView',
components: {
ProjectDashboardCard,
TheAddProjectButton,
},
data() {
return {
latestReports: [],
}
},
computed: {
...mapState(['user']),
},
mounted() {
this.fetchLatestReports()
},
methods: {
fetchLatestReports() {
const reportTuples = []
for (const repo of this.user.repositories) {
for (const service of this.user.services) {
reportTuples.push([service, repo])
}
}
const allReports = Promise.all(
reportTuples.map(t => finderService.find(t[0], t[1], true))
)
const latestReports = {}
allReports
.then(responses => {
const filteredResponses = responses.filter(
response => response.data.length > 0
)
filteredResponses.forEach(response => {
if (!latestReports[`repo${response.repoID}`]) {
latestReports[`repo${response.repoID}`] = {}
}
if (!latestReports[`repo${response.repoID}`].repoName) {
latestReports[`repo${response.repoID}`].repoName =
response.data[0].project_name
}
if (!latestReports[`repo${response.repoID}`].services) {
latestReports[`repo${response.repoID}`].services =
{}
}
latestReports[`repo${response.repoID}`].services[
response.service
] = response.data[0]
})
this.latestReports = latestReports
})
.catch(err => console.log(err))
},
},
}
</script>
Just in case, here's the code for the API call mock too:
import sonarqubeReport from './fixtures/find.sonarqube.1.json'
import gitleaksReport from './fixtures/find.gitleaks.1.json'
import dependencycheckReport from './fixtures/find.dependencycheck.1.json'
import hadolintReport from './fixtures/find.hadolint.1.json'
import trivyReport from './fixtures/find.trivy.1.json'
const mockGetResponse = service => {
switch (service) {
case 'sonarqube':
return sonarqubeReport
case 'gitleaks':
return gitleaksReport
case 'dependencycheck':
return dependencycheckReport
case 'hadolint':
return hadolintReport
case 'trivy':
return trivyReport
default:
break
}
}
const finderService = {
find: service => Promise.resolve({ body: mockGetResponse(service) }),
}
export default finderService

Related

Apollo Client is not working with NextJS using SSR

I'm trying to use Apollo Client/Server for my NextJS, and some pages I'm trying to SSR and staticlly generate them, but I'm faing an issue with Apollo where it fails to fetch on server but not when I use the client on the client.
Firstlly this is my package json, where my libs are configed:
{
"name": "landing",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev -p 9999",
"build": "next build",
"start": "next start -p 9999",
"lint": "next lint"
},
"dependencies": {
"#apollo/react-hooks": "^4.0.0",
"#reduxjs/toolkit": "^1.9.0",
"apollo-server-micro": "2.25.1",
"deepmerge": "^4.2.2",
"firebase": "^9.14.0",
"graphql": "^15.5.1",
"loadash": "^1.0.0",
"luxon": "^3.1.1",
"mongoose": "^5.13.15",
"next": "11.0.1",
"next-redux-wrapper": "^8.0.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-images-uploading": "^3.1.7",
"react-infinite-scroll-component": "^6.1.0",
"react-redux": "^8.0.5",
"react-tag-input-component": "^2.0.2",
"uuid": "^9.0.0"
},
"devDependencies": {
"autoprefixer": "^10.4.13",
"eslint": "7.32.0",
"eslint-config-next": "11.0.1",
"postcss": "^8.4.19"
}
}
And here is my single API route in pages/api.jsx folder:
import { Connect } from "../utils/Connect";
import { ApolloServer, makeExecutableSchema } from "apollo-server-micro";
import { defs as typeDefs } from "../graphql/defs";
import { resolves as resolvers } from "../graphql/resolves";
Connect();
export const schema = makeExecutableSchema({ typeDefs, resolvers });
const api = { bodyParser: false };
const path = { path: "/api" };
export const config = { api };
export default new ApolloServer({ schema }).createHandler(path);
While this is my Mongo Connection that I imported earlier:
import mongoose from "mongoose";
export const Connect = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true,
});
} catch (err) {
console.log(`Something went wrong trying to connect to the database);
process.exit(1);
}
};
While this is my Apollo Client which is configured for Client and Server calls:
import merge from "deepmerge";
import { useMemo } from "react";
import { ApolloClient, HttpLink, InMemoryCache, from } from "#apollo/client";
import { onError } from "#apollo/client/link/error";
import { concatPagination } from "#apollo/client/utilities";
import { isEqual } from "lodash";
export const APOLLO_STATE_PROP_NAME = "__APOLLO_STATE__";
let apolloClient;
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) graphQLErrors.forEach(({ message, locations, path }) => {
const msg = `[GraphQL error]: Message: ${message}`;
const lct = `Location: ${locations}`;
const pth = `Path: ${path}`;
console.log(`${msg}, ${lct}, ${pth}`);
});
if (networkError) {
const ntw = `[Network error]: ${networkError}`;
console.log(ntw);
}
});
const httpLink = new HttpLink({
uri: `http://localhost:9999/api`,
credentials: "same-origin",
});
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: from([errorLink, httpLink]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
allPosts: concatPagination(),
},
},
},
}),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
const existingCache = _apolloClient.extract();
const data = merge(existingCache, initialState, {
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
});
_apolloClient.cache.restore(data);
}
if (typeof window === "undefined") return _apolloClient;
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function addApolloState(client, pageProps) {
if (pageProps?.props) pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
return pageProps;
}
export function useApollo(pageProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(() => initializeApollo(state), [state]);
return store;
}
And lately, this is my page that is SSR-ing:
import { Category } from "../../ui/pages";
import { addApolloState, initializeApollo } from "../../config/Apollo";
import { Description, Globals, Title } from "../../config/Metas";
import { CATEGORIES, CATEGORY } from "../../graphql/queries";
export async function getStaticPaths() {
let paths = [];
const Apollo = initializeApollo();
const config = { query: CATEGORIES };
const categories = await Apollo.query(config);
paths = categories.data.Categories.map((category) => {
return {
params: {
category: category.Name.toLowerCase(),
...category,
},
};
});
return {
paths,
fallback: false,
};
}
export async function getStaticProps(context) {
const Apollo = initializeApollo();
const slug = context.params.category;
const category = await Apollo.query({
query: CATEGORY,
variables: { slug },
});
if (category) {
const data = { props: { category } };
return addApolloState(Apollo, data);
}
else return {
notFound: true,
};
}
const Route = ({ category }) => {
const { data } = category;
const { Category: Meta } = data;
return (
<>
<Globals />
<Title title={Meta.Name} />
<Description description={Meta.Description} />
<Category />
</>
);
};
export default Route;
All those queries that I call are being tested on the Apollo client on front and they are working, but when I do this and start the server with npm run dev I get a weird error:
[Network error]: TypeError: fetch failed
Error [ApolloError]: fetch failed
at new ApolloError (/Users/bfzli/Code/landdding/node_modules/#apollo/client/errors/errors.cjs:34:28)
at /Users/bf/Code/landing/node_modules/#apollo/client/core/core.cjs:1800:19
at both (/Users/bf/Code/landing/node_modules/#apollo/client/utilities/utilities.cjs:997:53)
at /Users/bf/Code/landing/node_modules/#apollo/client/utilities/utilities.cjs:990:72
at new Promise (<anonymous>)
at Object.then (/Users/bf/Code/landing/node_modules/#apollo/client/utilities/utilities.cjs:990:24)
at Object.error (/Users/bf/Code/landing/node_modules/#apollo/client/utilities/utilities.cjs:998:49)
at notifySubscription (/Users/bf/Code/landing/node_modules/zen-observable/lib/Observable.js:140:18)
at onNotify (/Users/bf/Code/landing/node_modules/zen-observable/lib/Observable.js:179:3)
at SubscriptionObserver.error (/Users/bf/Code/landing/node_modules/zen-observable/lib/Observable.js:240:7) {
type: 'ApolloError',
graphQLErrors: [],
clientErrors: [],
networkError: {
cause: {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '::1',
port: 9999
}
}
}

Auto-import vue reactivity system in vitest for testing composables in Nuxt 3 and vitest

I am using some utils in Nuxt 3. The vue reactivity system (ref, computed, ...) is also imported directly. However, it is not the case for the tests.
Running the spec file importing a ./useBusinessValidation composable throws the error ReferenceError: ref is not defined
Source file ./useBusinessValidation:
import { MaybeRef } from "#vueuse/core"
export const useBusinessValidation = <T>(rule: (payload: T) => true | string, payload: MaybeRef<T>) => {
const validation = computed(() => rule(unref(payload)))
const isValid = computed(() => validation.value === true)
const errorMessage = computed(() => isValid.value ? undefined : validation.value as string)
return {
isValid,
errorMessage
}
}
Spec file useBusinessValidation.spec.ts:
import { useBusinessValidation } from "./useBusinessValidation"
describe('useBusinessValidation', async () => {
it('should be valid with payload respecting the rule', () => {
const rule = (x: number) => x > 0 ? true : `invalid ${x} number. Expected ${x} to be greater than 0.`
const { isValid, errorMessage } = useBusinessValidation(rule, 0)
expect(isValid.value).toBe(true)
expect(errorMessage.value).toBe(undefined)
});
})
and the vitest.config.ts
{
resolve: {
alias: {
'~': '.',
'~~': './',
'##': '.',
'##/': './',
'assets': './assets',
'public': './public',
'public/': './public/'
}
},
test: {
globals: true,
setupFiles: './test/setupUnit.ts',
environment: 'jsdom',
deps: { inline: [/#nuxt\/test-utils-edge/] },
exclude: [
'test/**/**.spec.ts',
'**/node_modules/**',
'**/dist/**',
'**/cypress/**',
'**/.{idea,git,cache,output,temp}/**'
]
}
}
I also tried with the #vitejs/plugin-vue as
plugins: [Vue()]
in the vitest config. It didn't work out.
To auto-import in vitest, install the unplugin-auto-import.
Then, in the vitest.config.ts add:
import AutoImport from 'unplugin-auto-import/vite';
export default defineConfig({
...
plugins: [
AutoImport({
imports: [
'vue',
// could add 'vue-router' or 'vitest', whatever else you need.
],
}),
]
});

Uncaught TypeError: require.ensure is not a function at Object.getComponent (ECMASCRIPT)

Am porting over my meteor project to use ecmascript instead of using webpack / babel. Also upgrading my meteor (from 1.4 to 1.7) and react (from 15.3.2 to 16.8.6) too.
routes.jsx
import * as React from 'react';
export default function (injectDeps, {Store, Routes}) {
const route = {
path: 'tickets',
onEnter: (nextState, replace) => {
if (!Meteor.userId() || !Roles.userIsInRole(Meteor.userId(), 'staff', Roles.GLOBAL_GROUP)) {
replace('/login');
}
},
getComponent(nextState, cb) {
require.ensure([], (require) => {
Store.injectReducer('tickets', require('./reducers'));
cb(null, require('./containers/list.js'));
}, 'tickets');
},
childRoutes: [
{
path: ':_id',
getComponent(nextState, cb) {
require.ensure([], (require) => {
Store.injectReducer('tickets', require('./reducers'));
cb(null, require('./containers/view.js'));
}, 'tickets.view');
}
}
]
};
Routes.injectChildRoute(route);
}
Got this error:
Uncaught TypeError: require.ensure is not a function
at Object.getComponent (routes.jsx:20)
at getComponentsForRoute (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:38035)
at modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:38053
at modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:37842
at Array.forEach (<anonymous>)
at mapAsync (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:37841)
at getComponents (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:38052)
at finishEnterHooks (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:37263)
at next (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:37810)
at loopAsync (modules.js?hash=d04d5856a2fe2fa5c3dc6837e85d41adc321ecb2:37814
Any suggestion how to port this?
This is how it was done to get rid of require.ensure:
routes.jsx (final)
{
path: 'config',
getComponent(nextState, cb) {
import('./containers/config').then(mod => {
Store.injectReducer('config', require('./reducers/config').default);
cb(null, mod.default);
});
}
}
NOTE:
1) This is how the to migrate from require.ensure (used by webpack) to without relying on webpack (which was my case as am fully using Meteor Atmosphere to run)
2) mod and require(...).xxx had changed to mod.default and require(...).default if reducer function is exported as export default, otherwise said reducer will not be called.

React-Redux Testing with Jest: Received Payload = undefined

I am trying to learn/implement jest testing into my react-redux application. My test fails saying that the received does not equal what was expected, however, the actual thunk works and returns data to my application. So I've either written the test incorrectly (which i basically copy/pasted from the redux-docs) or I'm writing my thunk incorrectly.
ACTION
export const getOddGroups = () => {
return dispatch => {
return axios.get("/api/tables/oddgroups")
.then(results => {
dispatch({type: "GET_ODD_GROUPS", payload: results.data})
}).catch(err => {
dispatch({ type: "GET_ERRORS", payload: err.response.message })
})
}
}
TEST
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as oddActions from '../actions/OddActions';
import fetchMock from 'fetch-mock'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('query preview async actions', () => {
afterEach(() => {
fetchMock.restore()
})
it('creates GET_ODD_GROUPS when successful', () => {
fetchMock.get("*", {
results: { data: [{ "row1": "some data" }] },
headers: { 'content-type': 'application/json' }
})
const expectedActions = [
{ type: "GET_ODD_GROUPS", results: { data: [{ "row1": "some data" }] } },
]
const store = mockStore({ oddGroups: [] })
return store.dispatch(oddActions.getOddGroups()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
TEST RESULT OUTPUT:
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Array [
Object {
- "results": Object {
- "data": Array [
- Object {
- "row1": "some data",
- },
- ],
- },
- "type": "GET_ODD_GROUPS",
+ "payload": undefined,
+ "type": "GET_ERRORS",
},
]
EDIT - UPDATE
At the suggestion of #CoryDanielson I reworked the test using axios-mock-adapter and this post but I'm still getting the same error as above.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as oddActions from '../actions/oddActions';
import axios from "axios";
import MockAdapter from 'axios-mock-adapter';
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
let mock = new MockAdapter(axios);
describe('query preview async actions', () => {
beforeEach(function () {
/*Not sure which one is best to use in this situation yet
* will test both
*/
mock.reset(); // reset both registered mock handlers and history items with reset
//mock.restore(); //restore the original adapter (which will remove the mocking behavior)
});
it("return data for GET_ODD_GROUPS when successful", function (done) {
mock.onGet("api/tables/oddGroups")
.reply(function () {
return new Promise(function (resolve, reject) {
resolve([200, { key: 'value' }]);
});
});
const store = mockStore({ oddGroups: [] })
store.dispatch(oddActions.getOddGroups()).then(() => {
let expectedActions = [{ type: "GET_ODD_GROUPS", payload: { key: 'value' } }]
console.log(store.getActions());
expect(store.getActions()).toEqual(expectedActions);
});
setTimeout(() => {
done();
}, 1000)
});
});
LOGGING:
When I return the console state console.log(store.getActions());
Its giving me back the error dispatch action
And this console.log(store.dispatch(oddActions.getOddGroups())); returns Promise { <pending> }
FINAL SOLUTION:
After trying and failing with several options, I dropped using axios-mock-adapter and used moxios instead. After following this article I was able to successfully create tests.
Here is the solution without axios-mock-adapter, don't add too many things in your code, keep it simple. You can mock axios module manually by yourself, look at below code:
actionCreators.ts:
import axios from 'axios';
export const getOddGroups = () => {
return dispatch => {
return axios
.get('/api/tables/oddgroups')
.then(results => {
dispatch({ type: 'GET_ODD_GROUPS', payload: results.data });
})
.catch(err => {
dispatch({ type: 'GET_ERRORS', payload: err.response.message });
});
};
};
actionCreators.spec.ts:
import { getOddGroups } from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import axios from 'axios';
import { AnyAction } from 'redux';
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
jest.mock('axios', () => {
return {
get: jest.fn()
};
});
describe('actionCreators', () => {
describe('#getOddGroups', () => {
let store;
beforeEach(() => {
const initialState = {};
store = mockStore(initialState);
});
it('should get odd groups correctly', () => {
const mockedResponse = { data: 'mocked data' };
(axios.get as jest.MockedFunction<typeof axios.get>).mockResolvedValueOnce(mockedResponse);
const expectedActions = [{ type: 'GET_ODD_GROUPS', payload: mockedResponse.data }];
return store.dispatch(getOddGroups()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(axios.get).toBeCalledWith('/api/tables/oddgroups');
});
});
it('should get odd groups error', () => {
const mockedError = {
response: {
message: 'some error'
}
};
(axios.get as jest.MockedFunction<typeof axios.get>).mockRejectedValueOnce(mockedError);
const expectedActions = [{ type: 'GET_ERRORS', payload: mockedError.response.message }];
return store.dispatch(getOddGroups()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(axios.get).toBeCalledWith('/api/tables/oddgroups');
});
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/57730153/actionCreators.spec.ts
actionCreators
#getOddGroups
✓ should get odd groups correctly (5ms)
✓ should get odd groups error (2ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
actionCreators.ts | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.934s, estimated 4s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57730153

How to test graphql apollo component? How to get complete coverage inside compose()?

I'm having issues getting complete coverage on my apollo components. istanbul is reporting the functions inside compose() are not getting called. These are Redux connect() functions and apollo graph() functions.
export default compose (
...
connect(mapStateToProps, mapDispatchToProps), // <-- functions not covered
graphql(builderQuery, {
options: (ownProps) => { // <-- function not covered
...
)(ComponentName);
I'm mounting using enzyme, trying to do something similar to the react-apollo example.
const mounted = shallow(
<MockedProvider mocks={[
{ request: { query, variables }, result: { data: response.data } }
]}>
<ConnectedComponentName />
</MockedProvider>
);
The only way I've been able to achieve 100% coverage is if I export all of the functions and call them directly.
testing composed graphql/redux containers
Try something like this for your setup:
// mocks.js
import configureMockStore from 'redux-mock-store'
import { ApolloClient } from 'react-apollo'
import { mockNetworkInterface } from 'apollo-test-utils'
export const mockApolloClient = new ApolloClient({
networkInterface: mockNetworkInterface(),
})
export const createMockStore = configureMockStore()
This will set you up to properly test your containers:
// container-test.js
import { mount } from 'enzyme'
import { createMockStore, mockApolloClient } from 'mocks'
beforeEach(() => {
store = createMockStore(initialState)
wrapper = mount(
<ApolloProvider client={mockApolloClient} store={store}>
<Container />
</ApolloProvider>
)
})
it('maps state & dispatch to props', () => {
const props = wrapper.find('SearchResults').props()
const expected = expect.arrayContaining([
// These props come from an HOC returning my grapqhql composition
'selectedListing',
'selectedPin',
'pathname',
'query',
'bbox',
'pageNumber',
'locationSlug',
'selectListing',
'updateCriteria',
'selectPin',
])
const actual = Object.keys(props)
expect(actual).toEqual(expected)
})
testing graphql options
Because the graphql fn has a signature like graphql(query, config), you can export your config for testing in isolation for more granular coverage.
import { config } from '../someQuery/config'
describe('config.options', () => {
const state = {
bbox: [],
locationSlug: 'foo',
priceRange: 'bar',
refinements: 'baz',
userSort: 'buzz',
}
const results = {
points: [
{ propertyName: 'Foo' },
{ propertyName: 'Bar' },
],
properties: [
{ propertyName: 'Foo' },
{ propertyName: 'Bar' },
],
}
it('maps input to variables', () => {
const { variables } = config.options(state)
const expected = { bbox: [], locationSlug: 'foo', priceRange: 'bar', refinements: 'baz', userSort: 'buzz' }
expect(variables).toEqual(expected)
})
it('returns props', () => {
const response = { data: { loading: false, geo: { results } } }
const props = config.props(response)
expect(props.results).toEqual(results.properties)
expect(props.spotlightPoints).toEqual(results.points)
})
})

Resources