Test Redux-Saga 'yield call' - javascript

I'm trying to write a unit test to the following saga :
function * verifyCode(api){
let action = yield take(LoginTypes.VERIFY_CODE)
const phoneNumber = yield select(phoneNumberSelector)
try{
const response = yield call(api.verifyCode, phoneNumber, action.code)
if (response.ok){
let token = response.data.token
yield put(LoginActions.verifyCodeSuccess(response.data.token))
}
else {
yield put(LoginActions.verifyCodeFailure(response.error))
}
}
catch(error){
yield put(LoginActions.verifyCodeFailure(error))
}
}
All the tests pass up until the 'yield call' part, using the following (using test from 'tape'):
test('verify code flow', (assert) => {
let number = '0000000'
let code = '00000'
const gen = verifyCode(api)
assert.deepEqual(
gen.next({code: code}).value,
take(LoginTypes.VERIFY_CODE),
'wait for verify code action from user',
)
assert.deepEqual(
gen.next().value,
select(phoneNumberSelector),
'get phoneNumber from login state'
)
assert.deepEqual(
gen.next().value,
call(api.verifyCode, number, code),
'call verify code'
)
assert.end()
})
The error I get for the failure of this test is
operator: deepEqual
expected: |-
{ '##redux-saga/IO': true, CALL: { context: null, fn: [Function: verifyCode], args: [ '0000000', '00000' ] } }
actual: |-
{ '##redux-saga/IO': true, PUT: { channel: null, action: { type: 'VERIFY_CODE_FAILURE', error: [TypeError: Cannot read property 'code' of undefined] } } }
What is the correct way to write to an api call using the 'call' effect?
How can I test the different possible flows depending on the ''response I get?

This is the correct way to use the call effect on an API
To test different flows (Success | Failure) you can use the following pattern (in Jest):
expect(generator.next().value.PUT.action).toMatchObject({type: types.VERIFY_CODE_SUCCESS, payload: {}, meta: { section }});
expect(generator.throw('error').value.PUT.action).toMatchObject({ type: VERIFY_CODE_FAILURE, payload: 'error'});
BTW: It seems that your test fails due to an error thrown when you called your API endpoint. Make sure you get the desired response from your endpoint.
Good Luck!

Related

How to make a structure interface that return a response with status and json.?

I have a interface which describes a response payload.
export interface ObjectResponseInterface {
status : HttpStatusCode,
error : Boolean,
response : any
}
I create a response object typed to ObjectResponseInterface, but I want make a structure that always return this obj, objRes and res.status, and res.json(objRes.status) as follows
const objRes : ObjectResponseInterface = {
status : HttpStatusCode.BAD_REQUEST,
error : false,
response : response
}
res.status(objRes.status).json(objRes)
Is there a way to implement this response as a interface, maybe to extend Express?
If I miss something please correct me, please. I think you can make this with a generic approach.
For example
// Generic Definition
export interface ApiResponse<T> {
// "httpStatus" have to return the status as a number
// because res.status accepts a number as a parameter.
status: number,
error: boolean,
response: T
}
// Usage Examples
// Response as primitive type
const objRes: ApiResponse<string> = {
status: httpStatus.BAD_REQUEST,
error: false,
response: 'Request gone wrong'
}
res.status(objRes.status).json(objRes)
// Response as custom type
export type CustomType = {
id: number,
name: string
}
const objRes: ApiResponse<CustomType> = {
status: 200,
error: false,
response: { id: 1, name: 'test' }
}
res.status(objRes.status).json(objRes)

res in controller function fails due to being undefined

I'm new to Sails (and a jr. dev to boot) and have been working through the Sailsjs in Action book. Given some previous experience with TypeScript I wanted to experiment with the framework in that capacity. However I've hit a road block when trying to return response statuses and information via my API.
Every time I try to use the res in my controller I receive a:
TypeError: Cannot read property 'ok' of undefined. Based on the book and the documentation, I'm under the impression that would get set automatically per this example from the docs:
await User.create({name:'Finn'});
return res.ok();
and this example from the book:
signup: function(req, res) {
var options = {**grab request data**};
User.create(options).exec(function(err, createdUser){
if(err){
return res.negotiate(err);
}
return res.json(createdUser);
}
So I feel like I'm missing something pretty obvious but I'm not sure what. The project compiles just fine and I've got the documented typescript libraries installed/configured. Even matching that function there returns the same TypeError to me.
Another set of eyes would be greatly appreciated. Controller code below.
declare const sails: any;
import { boatInterface } from '../../interfaces/boat';
module.exports = {
friendlyName: 'new-boat',
description: 'create a new boat model',
inputs: {
modelName:{
required: true,
type: 'string',
},
yearBuilt:{
required: true,
type: 'number'
}
},
exits: {
success: {
description: 'New boat model was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided boat info was invalid.'
},
modelAlreadyCreated: {
statusCode: 409,
description: 'The provided boat model has already been
created.',
},
},
fn: async function (req: any, res: any){
console.log('building boat');
let boatRequest: boatInterface = {
modelName: req.modelName.toLowerCase(),
yearBuilt: req.yearBuilt
}
//confirming data has been formatted correctly
console.log(boatRequest);
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
//confirming new boat exists
console.log(newBoat);
console.log("request successful");
//res remains undefined and throws an error on attempted return
console.log(res);
return res.ok();
}
};
Here's the error with some console logs included. Thanks in advance!
building boat
{ modelName: 'kraken', yearBuilt: 1337 } <-- Request formats correctly
{ createdAt: 1566173040652,
updatedAt: 1566173040652,
id: 6,
modelName: 'kraken',
yearBuilt: 1337 } <-- new db entry is returned via fetch()
request successful
undefined <-- attempt to log the res returns undefined
(node:3738) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'ok' of undefined
at Object.<anonymous> (/Users/pitterpatter/Repos/learn/sails-learn/freeform/api/controllers/boat/new-boat.ts:67:20)
It looks like you're using actions2.
Your handler function will be given 2 parameters - inputs and exits as you've defined in the objects above it.
fn: async function (inputs: any, exits: any) {
inputs will be an object that contains parmeters given by a user that you've defined in the inputs part of your action (form data/query parameters/route parameters).
In this case it'd contain a modelName and yearBuilt, something like
{ modelName: 'lemon', yearBuilt: 2012.3 }
exits will contain a few default methods - exits.success() and exits.error(), as well as the invalid and modelAlreadyCreated which you've defined.
TLDR try this
fn: async function (inputs: any, exits: any) {
let boatRequest: boatInterface = {
modelName: inputs.modelName.toLowerCase(),
yearBuilt: inputs.yearBuilt
}
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
return exits.success(newBoat);
}
You can access the "raw express response object" deely that you're looking for by using this.res.ok(...) or this.req.something, but it's recommended you use the appropriate "exit" instead.

Can't unit test redux-saga with selector function using Jest

Problem Explanation:
I want to unit test a redux-saga using Jest. I'm doing this the way it is described in the example provided within the redux-saga docs: https://redux-saga.js.org/docs/advanced/Testing.html
Within my Saga I'm calling a selector function selectSet that returns a specific object from the application store:
export const selectSet = state => state.setStore.set
In my saga I'm trying to yield this selector function:
import { put, select } from 'redux-saga/effects'
import { selectSet } from '../selectors'
export function* getSet() {
try {
const set = yield select(selectSet)
yield put({ type: 'SET_SUCCESS', payload: { set } })
} catch (error) {
yield put({ type: 'SET_ERROR', payload: { error } })
}
}
Within my test there is no valid application store so I'd have to mock the function to return the expected object:
import assert from 'assert'
import * as AppRoutines from './AppRoutines'
import { put, select } from 'redux-saga/effects'
describe('getSet()', () => {
it('should trigger an action type "SET_SUCCESS" with a payload containing a valid set', () => {
const generator = AppRoutines.getSet()
const set = {
id: 1,
slots: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }],
}
const selectSet = jest.fn()
selectSet.mockReturnValue(set)
// Saga step 1
const actualFirst = generator.next().value
const expectedFirst = select(selectSet)
assert.deepEqual(
actualFirst,
expectedFirst,
'it should retreive a valid set from the store using the selectSet selector'
)
})
})
However - if I assert the saga to return a specific generator value using deepEqual and my mocked function, it expects my selector function to have the original selectSet constructor. But since I'm mocking the function with jest.fn() the constructor is actually equal to mockConstructor - which makes my test fail:
Expected value to deeply equal to:
{"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function mockConstructor]}}
Received:
{"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function selectSet]}}
Question: How can I make an assert.deepEqual containing a mock function without conflicting constructor types?
Alternative Question: Is there a way to make my assertion expect a mockConstructor instead of the actual selectSet constructor?
You should not need to mock the selector at all, as in a saga test of this nature, the selector is never actually called, instead you are testing the declarative instructions that are created for the redux saga middleware to act upon are as you expect
This is the instruction that the saga will create {"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function selectSet]}}, but as the middleware is not running during this test scenario selectSelect will never actually get called
If you need to mock results that your selector returns for your action, then you do so by passing the mock data into the next step...
const set = {
id: 1,
slots: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }],
}
// Saga step 1
const firstYield = generator.next().value
assertDeepEqual(firstYield, select(selectSet))
// Step 2 - successful so dispatch action
// mock data from the previous yield by passing into this step via `next(stubbedYieldedData)`
const secondYield = generator.next(set).value
assertDeepEqual(secondYield, put({type: 'SET_SUCCESS', payload: {set} }))
We can pass the mock store in a fake store as below. Below are the sample selector and generator function along with its test.
Selector
const authSelector = (state) => state.authReducer || initialState;
Saga Generator function
export function* getAuthToken(action) {
try {
const authToken = yield select(makeSelectAuthToken());
} catch (errObj) {}
}
Test Case
import { runSaga } from 'redux-saga'
const dispatchedActions = [];
const fakeStore = {
getState: () => ({ authReducer: { auth: 'test' } }),
dispatch: (action) => dispatchedActions.push(action)
}
await runSaga(fakeStore, getAuthToken, {
payload: {}
}).done;
expected case you can write here below this

JS decorator return original method if check succeeds

I've setup a simple decorator for my graphql mutations, all it's supposed todo is run an auth check on the request to see if it's authorised or not. I've got it to run the check & return an error if everything goes to plan, but when there is no error (request is authorised), it just hangs.
Here is my decorator code:
function checkAuth(target) {
target.mutateAndGetPayload = (input, { rootValue }) => {
if (!rootValue.request.user) {
return {
error: {
message: `Invalid authorization header`,
type: "invalid_auth",
client_message: `You do not have permission to access this resource`,
}
};
}
return target.mutateAndGetPayload;
};
return target;
}
And here is my mutation code (just a simple mutation):
const AddMedicalRecord = mutationWithClientMutationId({
name: 'AddMedicalRecord',
description: 'AddMedicalRecord',
inputFields: {
...fields here...
},
outputFields: {
error: {
type: ErrorType, // Simply: { client_message, message, type }
description: 'Description of the error if anything',
resolve: payload => payload.error
},
success: {
type: GraphQLString,
description: 'Description of the success',
resolve: payload => payload.success
}
},
#checkAuth
mutateAndGetPayload: (input, { rootValue }) => {
console.log(input, 'passed decorator :D ') // It never reaches here with decorator
It never reaches that console.log IF the request is authorized and the decorator does not return an error. Any help is appreciated as I've only used decorators a handful of times. I feel like it's something todo with the way I'm returning inside the target.mutateAndGetPayload

simple tape js test for redux saga failing with undefined

I've written a few tape tests for my sagas but the most simple examples are consistently failing for the same reason. I have a one line saga:
export function* clearUser(){
yield* put({type: 'CLEAR_USER'});
}
my tape test is equally simple:
test('clear user saga', (assert)=> {
const gen = clearUser();
assert.deepEqual(
gen.next().value,
put({type: 'CLEAR_USER'}),
'clear user should pass to reducer to remove user from state'
)
assert.deepEqual(
gen.next(),
{ done: true, value: undefined },
'clear user saga should complete'
)
assert.end()
});
However, the first assertion fails and says the value is undefined:
operator: deepEqual
expected: |-
{ '##redux-saga/IO': true, PUT: { action: { type: 'CLEAR_USER' }, channel: null } }
actual: |-
undefined
I've confirmed I'm importing the saga, and other tests are working, why does this simple test fail?
You need to yield your put effect, not delegate to another generator.
compare yield* vs yield

Categories

Resources