Testing redux-saga - redux

I am trying to implement a test for redux saga as follows but I ran into a problem. The error I get is cannot read property payload of undefined. The 'message' var that I passed to the saga function is undefined for some reason, can anyone tell me why? Thanks
saga.spec.js
import test from 'tape'
import { put,take,call } from 'redux-saga/effects'
import { onCreateMessage } from '../src/common/sagas/messages'
import { addMessage,createMessage } from '../src/common/reducers/messages'
test('createMessage', assert => {
const gen = onCreateMessage()
var message = {
id: 1234,
channelID: "AA",
text: "text",
user: "user"
}
assert.deepEqual(
gen.next(message).value,
put(addMessage(message)),
'createMessage should dispatch addMessage action'
)
})
saga/index.js
export default function* rootSaga(){
yield [
takeEvery('CREATE_MESSAGE', onCreateMessage),
......
]
When I console logged message below I get 'undefined'
export function* onCreateMessage(message) {
console.log(message)
yield put(addMessage(message.payload))
try {
yield call(fetch,'/api/newmessage',
{
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(message.payload)}
)
} catch (error){
throw error
}
}
I am using actionCreator from redux-actions:
export const addMessage = createAction(ADD_MESSAGE);

change
const gen = onCreateMesaage()
to
const gen = onCreateMessage(message)
Explanation:
message is the arguments passed to generator function rather than the yield result.
Previous code works for yielded result. e.g
export function* onCreateMessage() {
const message = yield
console.log(message)
yield put(addMessage(message.payload))
}

Related

Testing event emitted from child component with Vitest & Vue-Test-Utils

I want to test if "onLogin" event emitted from child component will trigger "toLogin" function from parent correctly.
Login.vue
<template>
<ChildComponent
ref="child"
#onLogin="toLogin"
/>
</template>
<script>
import { useAuthStore } from "#/stores/AuthStore.js"; //import Pinia Store
import { userLogin } from "#/service/authService.js"; // import axios functions from another js file
import ChildComponent from "#/components/ChildComponent.vue";
export default {
name: "Login",
components: {
ChildComponent,
},
setup() {
const AuthStore = useAuthStore();
const toLogin = async (param) => {
try {
const res = await userLogin (param);
AuthStore.setTokens(res);
} catch (error) {
console.log(error);
}
};
}
</script>
login.spec.js
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { shallowMount, flushPromises } from '#vue/test-utils';
import { createTestingPinia } from "#pinia/testing";
import Login from "#/views/user/Login.vue"
import { useAuthStore } from "#/stores/AuthStore.js";
describe('Login', () => {
let wrapper = null;
beforeAll(() => {
wrapper = shallowMount(Login, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
},
});
})
it('login by emitted events', async () => {
const AuthStore = useAuthStore();
const loginParam = {
email: 'dummy#email.com',
password: '12345',
};
const spyOnLogin = vi.spyOn(wrapper.vm, 'toLogin');
const spyOnStore = vi.spyOn(AuthStore, 'setTokens');
await wrapper.vm.$refs.child.$emit('onLogin', loginParam);
await wrapper.vm.$nextTick();
await flushPromises();
expect(spyOnLogin).toHaveBeenCalledOnce(); // will not be called
expect(spyOnStore).toHaveBeenCalledOnce(); // will be called once
})
}
I expected both "spyOnLogin" and "spyOnStore" will be called once from emitted event, however, only "spyOnStore" will be called even though "spyOnStore" should only be called after "spyOnLogin" has been triggered.
The error message is:
AssertionError: expected "toLogin" to be called once
❯ src/components/__tests__:136:24
- Expected "1"
+ Received "0"
What do I fail to understand about Vitest & Vue-Test-Utils?
You shouldn't mock your toLogin method because its part of Login component which you are testing. Therefore, instead of expecting if toLogin has been called, you should check if instructions inside are working correctly.
In your case i would only test if after emit, userLogin and AuthStore.setTokens has been called.

Next Framework won't even return me a console.log

Just as the title said, i cannot fetch data from a json neither display a console log from _app.js in NextJS.
My code:
//pages/_app.js
import '../css/font-awesome.min.css'
import '../css/owl.carousel.css'
import '../css/owl.transitions.css'
import '../css/animate.min.css'
import '../css/lightbox.css'
import 'bootstrap/dist/css/bootstrap.css'
import '../css/preloader.css'
import '../css/image.css'
import '../css/icon.css'
import '../css/style.css'
import '../css/responsive.css'
import '../styles/globals.css'
import 'react-responsive-carousel/lib/styles/carousel.min.css'
function MyApp({ Component, pageProps, json }) {
return <Component {...pageProps} json={json}/>
}
export async function getStaticProps() {
console.log("test")
const res = await api.get("./api/test.json");
console.log(res)
const json = res.data;
console.log(json);
return {
props: {
json,
},
};
}
export default MyApp
I doesn't return anything in console... I need to link the frontend and the backend but i cannot make it to work. Tried with getStaticProps, getInitialProps, etc... But it doesn't return anything on console. Even when the function is called it gives me undefined with a local json.
Another approach
//pages/api/bienvenido.js
const Bienvenido = ({ bienvenidos, error }) => {
if (error) {
return <div>An error occured: {error.message}</div>;
}
return (
<ul>
{bienvenidos.map(bienvenido => (
<li key={bienvenido.id}>{bienvenido.id}</li>
))}
</ul>
);
};
Bienvenido.getInitialProps = async ctx => {
try {
// Parses the JSON returned by a network request
const parseJSON = resp => (resp.json ? resp.json() : resp);
// Checks if a network request came back fine, and throws an error if not
const checkStatus = resp => {
if (resp.status >= 200 && resp.status < 300) {
return resp;
}
return parseJSON(resp).then(resp => {
throw resp;
});
};
const headers = {
'Content-Type': 'application/json',
};
const bienvenidos = await fetch('test.json', {
method: 'GET',
headers,
})
.then(checkStatus)
.then(parseJSON);
return { bienvenidos };
} catch (error) {
return { error };
}
};
export default Bienvenido;
This returns Undefined on this line "const Bienvenido = ({ bienvenidos, error }) => {"

Correct type for spawn when running flow-typed?

I'm running a project using Flowtype v0.75 and redux-saga
Currently I'm experiencing some issues trying to determine how to provide the right types for my sagas, specially when I'm trying to combine them:
ActivateAccountSaga.js
// #flow
import Alert from 'react-s-alert';
import { combineSagas, handleError } from 'util/Saga';
import { call, put, take, all } from 'redux-saga/effects';
import { history } from 'modules/LocationModule';
import {
activateAccount,
sendActivationEmail,
sendActivationEmailPending,
sendActivationEmailFulfilled,
sendActivationEmailRejected,
} from 'bundles/Auth/modules/ActivateAccountModule';
import AuthAPI from 'bundles/Auth/apis/AuthAPI';
import config from 'config/index';
export function* activateAccountWorker(api: AuthAPI): Generator<*, *, *> {
while (true) {
const { payload } = yield take(activateAccount().type);
try {
const response = yield call([api, api.activateAccount], payload);
yield call(history.push, config.route.identity.signIn);
yield call(Alert.success, response.description);
} catch (e) {
yield call(history.push, config.route.identity.signIn);
yield call(handleError, e);
}
}
}
export function* sendActivationEmailWorker(api: AuthAPI): Generator<*, *, *> {
while (true) {
const { payload } = yield take(sendActivationEmail().type);
try {
yield put(sendActivationEmailPending());
const response = yield call([api, api.sendActivationMail], payload);
yield put(sendActivationEmailFulfilled(response));
yield call(Alert.success, response.description, { timeout: 30000 });
yield call(history.push, config.route.identity.signIn);
} catch (e) {
yield put(sendActivationEmailRejected(e));
yield call(handleError, e);
}
}
}
export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
yield all(combineSagas([
[activateAccountWorker, api],
[sendActivationEmailWorker, api],
]));
}
const api = new AuthAPI();
export default [activateAccountSaga, api];
And this is the util/Saga.js
// #flow
import get from 'lodash/get';
import Alert from 'react-s-alert';
import { actions } from 'react-redux-form';
import { call, put, spawn, all, select } from 'redux-saga/effects';
export const refineSaga = (saga: * | Array<*>) => {
// Saga is a generator function without params
if (typeof saga === 'function') {
return [saga];
}
// Ensures that a saga in the form [saga, params...] was given
if (Array.isArray(saga) && typeof saga[0] === 'function') {
return saga;
}
throw new TypeError(`Unexpected saga type: ${saga.toString()}`);
};
export const combineSagas = (sagas: Array<Function | *>): any[] => sagas.map(saga => spawn(...refineSaga(saga)));
When running flow v0.75 I get the following error:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/bundles/Auth/sagas/ActivateAccountSaga.js:48:6
generator function [1] requires another argument from function type [2] in type argument Fn.
src/bundles/Auth/sagas/ActivateAccountSaga.js
[1] 16│ export function* activateAccountWorker(api: AuthAPI): Generator<*, *, *> {
:
45│
46│ export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
47│ yield all(combineSagas([
48│ [activateAccountWorker, api],
49│ [sendActivationEmailWorker, api],
50│ ]));
51│ }
flow-typed/npm/redux-saga_v0.16.x.js
[2] 863│ <R, Fn: () => R>(fn: Fn): SpawnEffect<null, Fn, []>,
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/bundles/Auth/sagas/ActivateAccountSaga.js:48:29
A callable signature is missing in AuthAPI [1] but exists in function type [2] in type argument Fn.
src/bundles/Auth/sagas/ActivateAccountSaga.js
45│
[1] 46│ export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
47│ yield all(combineSagas([
48│ [activateAccountWorker, api],
49│ [sendActivationEmailWorker, api],
50│ ]));
51│ }
flow-typed/npm/redux-saga_v0.16.x.js
[2] 863│ <R, Fn: () => R>(fn: Fn): SpawnEffect<null, Fn, []>,
As you can see flow tells me that I should provide another argument, but I'm doing it in an array based fashion, what is the alternative so flow is happy with it?
The issue was in here:
export const refineSaga = (saga: * | Array<*>): Array<*> =>
and
export const combineSagas = (sagas: any[]): any[] => sagas.map(saga => spawn(...refineSaga(saga)));

Redux: TypeError: e is undefined

https://github.com/reduxjs/redux/issues/3017
Problem: Occurs when I wrap my action creator with a dispatch in the container area where I utilize the connect method--I followed the style from redux documentation.
I am utilizing redux, and redux thunk. I am attempting to create a login action, so far it does not work when I dispatch an action, which dispatch's an another one.
LoginContainer.js
import CONFIG from "../../../config";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {authenticateUser} from "../../../actions/authenticateUser";
import Login from '../../../components/views/login/Login'
import {store} from '../../../store';
function handleSubmit(e) {
e.preventDefault();
let calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
store.dispatch(authenticateUser(calpersId))
}
const mapStateToProps = (state) => {
return {
authentication: state.authentication
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (e) => {dispatch(handleSubmit(e))}
}
}
const LoginContainer = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginContainer;
authenticateUser.action.js
import CONFIG from '../config'
export const AUTHENTICATE_USER = 'AUTHENTICATE_USER'
export const initiateUserAuthentication = (token) => ({
type: AUTHENTICATE_USER,
token
})
export const AUTHENTICATATION_SUCCEEDED = 'AUTHENTICATATION_SUCCEEDED'
export const authenticatationSucceeded = (payload) => ({
type: AUTHENTICATE_USER,
payload
})
export const USER_ID_DOES_NOT_EXIST = 'USER_ID_DOES_NOT_EXIST'
export const userIdDoesNotExist = (uid) => ({
type: USER_ID_DOES_NOT_EXIST,
uid,
message: "User id does not exist"
})
export function authenticateUser(id) {
return function (dispatch) {
let guidMap = {
7103503579: "dad08fde-0ac1-404a-ba8a-cc7c76d5810f",
6632408185: "6632408185-guid",
6581985123: "6581985123-guid",
1226290314: "a3908aa7-c142-4752-85ea-3741cf28f75e",
4618604679: "4618604679-guid",
6452522440: "6452522440-guid",
3685610572: "3685610572-guid",
5564535492: "5564535492-guid",
5600493427: "5600493427-guid",
3996179678: "3996179678-guid",
7302651964: "7302651964-guid",
3148148090: "3148148090-guid",
5826752269: "5826752269-guid",
6827859055: "6827859055-guid",
1677401305: "1677401305-guid",
2640602392: "dbed1af6-0fc9-45dc-96a3-ab15aa05a7a2",
6474994805: "6474994805-guid"
};
let guid = guidMap[id]
return fetch(CONFIG.API.MY_CALPERS_SERVER.LOCATION + 'ept/development/rest/simulatedAuth.json?guid=' + guid, {
credentials: 'include'
})
.then(
response => response.json(),
error => console.log('An error occured.', error))
.then(json => {
document.cookie = "authentication=" + guid + "; max-age=" + (60 * 30);
dispatch(authenticatationSucceeded(json))
})
}
}
authenticateUser.reducer.js
import {AUTHENTICATE_USER, AUTHENTICATATION_SUCCEEDED} from "../actions/authenticateUser";
const initialState = {
calpersIds: [
5600493427,
6474994805,
6452522440,
5564535492,
6632408185,
4618604679,
5826752269,
3996179678,
7302651964,
1677401305,
6827859055,
3685610572,
6581985123,
3148148090
],
guidMap: {
7103503579: "dad08fde-0ac1-404a-ba8a-cc7c76d5810f",
6632408185: "6632408185-guid",
6581985123: "6581985123-guid",
1226290314: "a3908aa7-c142-4752-85ea-3741cf28f75e",
4618604679: "4618604679-guid",
6452522440: "6452522440-guid",
3685610572: "3685610572-guid",
5564535492: "5564535492-guid",
5600493427: "5600493427-guid",
3996179678: "3996179678-guid",
7302651964: "7302651964-guid",
3148148090: "3148148090-guid",
5826752269: "5826752269-guid",
6827859055: "6827859055-guid",
1677401305: "1677401305-guid",
2640602392: "dbed1af6-0fc9-45dc-96a3-ab15aa05a7a2",
6474994805: "6474994805-guid"
},
authToken: null,
isAuthenticated: false
};
//#TODO: All fetches, create a seperate reducer for store?
export function authenticateUser(state = initialState, action) {
switch(action.type) {
case AUTHENTICATE_USER:
return Object.assign({}, state, {
authToken: action.token,
})
case AUTHENTICATATION_SUCCEEDED:
return Object.assign({}, state, {
authToken: action.payload.guid,
isAuthenticated: true,
payload: action.payload
})
default:
return state;
}
};
You should'nt use connect mapDispatchToProps like you are doing.
This callback is supposed to create or use functions that will dispatch an action.
For your case you can use it like that:
const mapDispatchToProps = (dispatch) => {
return {
authenticate: calpersId => authenticateUser(calpersId)(dispatch)
}
}
And in your component have a function/method that handle the submit:
class Login extends Component {
...
handleSubmit = e => {
e.preventDefault();
const calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
this.props.authenticate(calpersId)
}
...
By the way a reducer is supposed to represent the state of an entity. An entity named autenticateUser is pretty ambigious. You should propably named it user. You should read more redux examples to really catch the concept that at first a bit complicated to understand. There are good videos on Youtube.
Turns out I was calling an action creator which did not exist, I simply needed to pass my dispatch to the handler, and let it handle the the event.
Login.js
import CONFIG from "../../../config";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {authenticateUser} from "../../../actions/authenticateUser";
import Login from '../../../components/views/login/Login'
function handleSubmit(e, dispatch) {
e.preventDefault();
let calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
dispatch(authenticateUser(calpersId))
}
const mapStateToProps = (state) => {
return {
authentication: state.authentication
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (e) => {handleSubmit(e, dispatch)}
}
}
const LoginContainer = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginContainer;
What is the proper way of doing this, I utillized bindActionCreators which yields the same result.

redux not picking up an object dispatched via actions

I created a rootSaga in sagas.js as
function* fetchStuff(action) {
try {
yield put({type: 'INCREMENT'})
yield call(delay, 1000)
yield put({type: 'DECREMENT'})
const highlights = yield call(API.getStuff, action.data.myObject);
} catch (e) {
yield put({type: 'FETCH_STUFF_FAILED', message: e});
}
}
export default function* rootSaga() {
yield takeEvery('INIT_LOAD', fetchStuff);
}
I am calling the INIT_LOAD after thirdParty.method:
class myClass extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.load();
}
load = () => {
this.init = () => {
this.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObject: this.myObject
}
};
store.dispatch(action);
});
};
this.init();
};
render() {
return (
<div id="render-here" />
);
}
Passing the this.myObject in the action that is dispatched does not trigger the saga. If I change the action payload to a string, like the following, the saga is triggered.
const action = {
type: 'INIT_LOAD',
payload: {
myObject: 'this.myObject'
}
};
Why am I unable to pass this.myObject but a string is ok?
UPDATE: It is not a saga issue. I replicated the same issue with just plain redux. The rootReducer as
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case 'INIT_LOAD':
return Object.assign({}, state, { myObject: action.payload.myObject });
default:
return state;
}
}
As I mentioned in the comment below, assigning it to an object Obj does not change the issue
let Obj = {};
...
load = () => {
this.init = () => {
Obj.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObj: Obj
}
};
store.dispatch(action);
});
};
this.init();
};
UPDATE2
I cleaned the code up & simply dispatched an action in the component that triggers the saga. Inside the saga is where I do the init(). I ran into another issue where the object that I was trying to save in the redux store has active socket sessions (which were given me cross-domain issues). Although I didn't solve my original problem, not storing a socket object made my problem go away.

Resources