Testing ngrx store: No provider for MockStore - ngrx

I am following the Official Documentation for Testing ngrx Stores: https://ngrx.io/guide/store/testing
Even the simplest implementation of injecting a MockStore has the following Error:
NullInjectorError: R3InjectorError(CompilerModule)[MockStore -> MockStore]:
NullInjectorError: No provider for MockStore!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'MockStore', 'MockStore' ] })
My code looks like this:
import { TestBed } from '#angular/core/testing';
import { provideMockStore, MockStore } from '#ngrx/store/testing';
describe('Auth Guard', () => {
// #ts-ignore
let store: MockStore;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// any modules needed
],
providers: [
provideMockStore(),
// other providers
],
});
store = TestBed.inject(MockStore);
});
it('should create', () => {
expect(store).toBeTruthy();
});
});
I am Running #ngrx/store#8.6.0

UPDATED
based on discussion store = TestBed.inject(Store); instead of MockStore is enough for the desired behavior.
ORIGINAL
It is too early,
get it in the test:
it('should create', inject([Store], (store) => {
expect(store).toBeTruthy();
}));
not sure, but you can try to call compileComponents.
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
// any modules needed
],
providers: [
provideMockStore(),
// other providers
],
}).compileComponents();
store = TestBed.inject(MockStore);
});

Related

Trying to get "board" from "boards" use Pinia getter and receive undefined

what do i wrong?
I try to get the name of the board from Pinia Store.
I fetch the data from API onMounted in parent component (composition api setup):
onMounted(() => {
boardStore.fetchBoards();
});
and store the data in Pinia Store:
state: () => ({
boards: [],
activeBoardId: storage.getItem('activeBoardId') || null,
}),
actions: {
async fetchBoards() {
await apiClient
.getBoards()
.then((response) => {
this.boards = response.data;
return response;
})
.catch((err) => {
return err.response.data;
});
},
},
getters: {
getBoardById: (state) => {
return (id) => state.boards.find((board) => board.id === id);
},
getActiveBoardId: (state) => {
return state.activeBoardId;
},
},
In my component i try to get the name from the board via getBoardById():
import { useBoardStore } from '#stores/boardStore';
const boardStore = useBoardStore();
let board = boardStore.getBoardById(boardStore.getActiveBoardId).name
I expect to get the name of the board from the Pinia Store, but receiving undefined.
i also tried to use computed:
let board = computed(() => boardStore.getBoardById(boardStore.getActiveBoardId));
and can see the values:
ComputedRefImpl {dep: undefined, __v_isRef: true, __v_isReadonly: true, _dirty: true, _setter: ƒ, …}
...
value: Proxy
[[Target]]: Object
...
id: 95
name: "Privat"
...
But when i tried to use access i get undefined:
console.log(board)
console.log(board.name)
console.log(board.value)
console.log(board.value.name)
Also wehn i check Vue Dev Tools -> Pinia i see the "boards" Array with some items object. When i open one, i can see the "name":
boards:Array[2]
0:Object
...
id:95
name:"Privat"
...
1:Object
...
id:97
name:"Work"
...
activeBoardId:97
I suspect that either I'm trying to access them incorrectly or they're doing it too early, that the data has not yet hit the store, because async.
It's probably a small thing, but i can not find the bug or fix :-)
I expect to get the name of the board from the Pinia Store, but receiving undefined.
I found a solution, maybe not the best one, but it's work:
onMounted(async () => {
await boardStore.fetchBoards();
setBoardName();
});
watch(
() => boardStore.activeBoardId,
() => setBoardName()
);
const setBoardName = () => {
const board = computed(() => boardStore.getBoardById(boardStore.activeBoardId));
resetForm({
values: {
name: board.value.name,
},
});
};

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.
],
}),
]
});

How to create a route guard with Vue + Firebase (Google Identity Platform)

I've implemented Firebase (aka. Google Identity Platform) into my Vue project. I want to protect specific routes, so I've added the following:
// router/index.js
{
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter: (to, from, next) => {
if (firebase.auth().currentUser) {
next()
} else {
next({
path: '/login',
})
}
}
},
This works! However, it would become unmanageable if I did that for every route.
To make it tidy, I tried putting it into a function (within the route file and tried externally) but it won't work because Firebase hasn't been initialized at the time it is parsed so it throws an error saying to initialize Firebase first.
Firebase is initialised in my main.js file:
// main.js
// Firebase configuration
var firebaseConfig = {
// Config details redacted
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
Ideally what I am after is something similar to what Auth0 provides in there SDK example:
// router/index.js
//...some route
beforeEnter: authGuard()
Then authGuard would be in an external file. This file would hold the function that checks if a user is authenticated or not. Then I can add it to routes as needed.
Use beforeEach router hook and check for route metadata. Here is a sample code from one of my apps
let router = new Router({
routes: [
{path: '*', redirect: "/"},
{
path: "/",
name: 'login',
component: Login
},
{
path: "/register",
name: 'register',
component: Register,
},
{
path: "/home",
name: "home",
component: Home,
meta: {
requiresAuth: true
}
}
]
},
],
mode: 'history'
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser;
console.log("firebasedata",currentUser);
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!currentUser) {
next({
path: '/login',
query: {redirect: to.fullPath}
})
} else {
if(to.matched.some(record => record.name==='login')){
next({
path: '/home',
query: {redirect: to.fullPath}
})
}
else {
next();
}
}
} else {
next();
}
})
export default router
Import firebase Auth from your firebase config file, check if there is a current authenticated user in the cache. is there is then all routes can be accessed, if not use the "requiresAuth" variable to restrict access
import { auth } from '../plugins/firebase.js' //import auth from firebase config file
const routes = [
{
path: '/',
component: () => import('#/layouts/default/Default.vue'),
children: [
{
path: '',
name: 'login',
component: () => import('#/views/auth/Login.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('#/views/auth/Register.vue'),
},
{
path: '/forgotPassword',
name: 'forgotPassword',
component: () => import('#/views/auth/ForgotPassword.vue'),
},
{
path: '/app',
name: 'app',
component: () => import('#/views/app/Dashboard.vue'),
meta: {
requiresAuth: true // the route you want to protect
},
},
],
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
//Protection code
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(x => x.meta.requiresAuth)
const user = auth.currentUser
if (requiresAuth && !user) next('/')
else if (requiresAuth && user) next()
else next()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

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

NGRX Entity test in component

I have a component with an entity selector 'selectAllProperties' in ngOnInit, and I want to test this component:
ngOnInit() {
this.store.dispatch(new LoadPropertiesRequested());
this.properties$ = this.store.pipe(select(selectAllProperties));
this.loading$ = this.store.pipe(select(selectPropertiesLoading));
this.logs$ = this.store.pipe(select(selectPropertiesLogs));
}
In my spec file, i initialized the store like in the ngrx doc:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({
...fromRoot.reducers,
feature: combineReducers(fromProperties.reducer),
})
],
declarations: [
SidebarPropertiesComponent,
SidebarElementComponent
]
})
.compileComponents();
}));
When I launch the tests, I have 'TypeError: Cannot read property 'ids' of undefined'.
All the others selectors do not produce errors
I also would like to mock the Observable returned by each selector.
Thanks
I have found the problem, in TestBed.configureTestingModule
instead of
imports: [
StoreModule.forRoot({
...fromRoot.reducers,
feature: combineReducers(fromProperties.reducer),
})
],
use
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
StoreModule.forFeature('properties', fromProperties.reducer),
],
No more 'TypeError: Cannot read property 'ids' of undefined'
I can mock the properties
it('should have 2 properties elements', () => {
store.dispatch(new LoadPropertiesSuccess({properties: propertiesMock}));
fixture.detectChanges();
const list = debugElement.queryAll(By.css('li'));
expect(list.length).toBe(2);
});

Resources