ReactJs - test multiple calls in redux-saga with expectSaga - unit-testing

I'm using expectSaga ('redux-saga-test-plan') to test one of my sagas and I'm wondering how to test multiple calls made within the same saga.
Sagas.js
export function* fetchSomething(arg){
const response = yield call(executeFetch, arg);
if(response.status === 200){
// trigger success action
} else if (response.status >= 400){
const errResp = yield response.json();
const errorCode = yield call(sharedUtilToExtractErrors, errResp);
yield put(
{ type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, errorCode }
);
}
}
Unit test
import { expectSaga } from 'redux-saga-test-plan';
describe('fetchSomething', () => {
// positive paths
// ..
// negative paths
it('fetches something and with status code 400 triggers FETCH_FAILED with error message and extracted error code', () => {
const serverError = { message: 'BANG BANG BABY!' };
const koResponse = new Response(
JSON.stringify(serverError),
{ status: 400, headers: { 'Content-type': 'application/json' } }
);
return expectSaga(fetchSomething)
.provide(
{
call: () => koResponse,
call: () => serverError.message,
}
)
.put({
type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, serverError.message
})
.run();
})
})
Clearly having the "call" attribute twice in the same object passed in to provide() doesn't work but also calling provide() twice doesn't do the trick. Any suggestions?
Thanks

This is how you can provide multiple calls according to the documentation:
.provide([ // this external array is actually optional
[call(executeFetch, arg), koResponse],
[call(sharedUtilToExtractErrors, serverError), serverError.message],
])
or if you're lazy and don't want to specify the arguments:
import * as matchers from 'redux-saga-test-plan/matchers';
.provide(
[matchers.call.fn(executeFetch), koResponse],
[matchers.call.fn(sharedUtilToExtractErrrors), serverError.message],
)
Neither of these two worked for me though as for some reason it was not mocking out the dependencies and still calling them caused errors.
I solved using a dynamic provider:
.provide({
// select(effect, next) { return 'something-for-a-selector' },
call(effect) {
switch(effect.fn.constructor.name) {
case executeFetch.constructor.name: return koResponse;
case sharedUtilToExtractErrors.constructor.name: return serverError.message;
default: throw new Error('Unknown function called in test');
}
}
})

Related

jest.spyOn mock return value not returning value

The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)

How i can CATCH any error (network and graphql), and prevent promise.reject()?

i Already can get Apollo Client errors:
import {onError} from 'apollo-link-error'
function createLink() {
return ApolloLink.from([
createErrorLink(),
createHttpLink(),
])
}
function createErrorLink() {
return onError(function ({graphQLErrors, networkError}) {
if (graphQLErrors) {
graphQLErrors.map(({message}) => {
console.log(`[GraphQL error]`, message)
})
}
if (networkError) {
console.log(`[Network error]`, networkError)
}
})
}
Yes, error handlers works, but how i can PREVENT promise rejection?
const errorLink = onError(({networkError, graphQLErrors, response}) => {
// DO ANYTHING
// Stop error propagation to next Links
response.errors = null
})
HttpLink not see errors end not reject promise =)

Write a mocha unit test code for the following meteor code

I want to know how to write test code using mocha for the meteor function
export const physicalToLogical = (physicalStatus, planningStartDate, planningEndDate) => {
if(physicalStatus === STATUS_PHYSICAL_CREATING) {
return STATUS_LOGICAL_CREATING;
} else if (physicalStatus === STATUS_PHYSICAL_OPEN) {
const now = new Date();
if(planningStartDate.getTime() <= now && planningEndDate.getTime() > now) {
return STATUS_LOGICAL_OPEN_FOR_PLAN;
} else if(planningStartDate.getTime() > now) {
return STATUS_LOGICAL_PROSPECT;
}
return STATUS_LOGICAL_REVIEW;
} else if (physicalStatus === STATUS_PHYSICAL_CLOSED) {
return STATUS_LOGICAL_CLOSED;
} else if (physicalStatus === STATUS_PHYSICAL_ARCHIVED) {
return STATUS_LOGICAL_ARCHIVED;
}
throw new Error("Not implemented yet");
};
First, this function has nothing to do with Meteor.
Writing tests for such a function would involve sending different statuses to the method and expecting the different results.
Here's an example (using chai as the assertions library) :
describe('physicalToLogical', () => {
it('should return the given status', () => {
expect(physicalToLogical(STATUS_PHYSICAL_CREATING, null, null)).
toEqual(STATUS_LOGICAL_CREATING);
});
it('should...', () => {
...
});
...
});
This is one of the many simple cases you have to write for that code.
Several other tests need to be written for the cases involving dates, but the format is more or less the same.

How to return error response in apollo link?

I'm using apollo link in schema stitching as an access control layer. I'm not quite sure how to make the link return error response if a user does not have permissions to access a particular operation. I know about such packages as graphql-shield and graphql-middleware but I'm curious whether it's possible to achieve basic access control using apollo link.
Here's what my link looks like:
const link = setContext((request, previousContext) => merge({
headers: {
...headers,
context: `${JSON.stringify(previousContext.graphqlContext ? _.omit(previousContext.graphqlContext, ['logger', 'models']) : {})}`,
},
})).concat(middlewareLink).concat(new HttpLink({ uri, fetch }));
The middlewareLink has checkPermissions that returns true of false depending on user's role
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
const hasAccess = checkPermissions({ operationName, context, variables });
if (!hasAccess) {
// ...
}
}
return forward(operation);
});
What should I do if hasAccess is false. I guess I don't need to forward the operation as at this point it's clear that a user does not have access to it
UPDATE
I guess what I need to do is to extend the ApolloLink class, but so far I didn't manage to return error
Don't know if anyone else needs this, but I was trying to get a NetworkError specifically in the onError callback using Typescript and React. Finally got this working:
const testLink = new ApolloLink((operation, forward) => {
let fetchResult: FetchResult = {
errors: [] // put GraphQL errors here
}
let linkResult = Observable.of(fetchResult).map(_ => {
throw new Error('This is a network error in ApolloClient'); // throw Network errors here
});
return linkResult;
});
Return GraphQL errors in the observable FetchResult response, while throwing an error in the observable callback will produce a NetworkError
After some digging I've actually figured it out. But I'm not quite sure if my approach is correct.
Basically, I've called forward with a subsequent map where I return an object containing errors and data fields. Again, I guess there's a better way of doing this (maybe by extending the ApolloLink class)
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
try {
checkPermissions({ operationName, context, variables });
} catch (err) {
return forward(operation).map(() => {
const error = new ForbiddenError('Access denied');
return { errors: [error], data: null };
});
}
}
return forward(operation);
});

How to signal the caller about the state of a promise

I have Ember code where the backend API calls are abstracted into a
separate service. This service uses ember-ajax library for making
backend calls.
This service sets up the common headers, handles the
timeout errors, and 4xx/5xx errors. And anything else like 422
(validation errors) are left to be handled by the calling code.
-
getCustomerProfile (authenticationToken) {
const backendService = this.get('callBackendService');
return backendService.callEndpoint(GET_METHOD,
getCustomerProfileAPI.url,
{'X-Auth-Token': authenticationToken}).then((customerProfileData) => {
if (!backendService.get('didAnybodyWin') && customerProfileData) {
backendService.set('didAnybodyWin', true);
return customerProfileData.profiles[0];
}
}).catch((error) => {
if (isInvalidError(error)) {
if (!backendService.get('didAnybodyWin')) {
backendService.set('didAnybodyWin', true);
backendService.transitionToErrorPage();
return;
}
}
});
}
and the call-backend-service looks like this
callEndpoint (httpVerb, endPoint, headersParameter, data = {},
timeoutInMillisecs = backendCallTimeoutInMilliseconds) {
const headersConst = {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json',
'Brand': 'abc'
};
var headers = Ember.assign(headersParameter, headersConst);
var promiseFunctionWrapper;
this.set('didAnybodyWin', false);
if (httpVerb.toUpperCase() === GET_METHOD) {
Ember.Logger.warn('hit GET Method');
promiseFunctionWrapper = () => {
return this.get('ajax').request(endPoint, {headers});
};
} else if (httpVerb.toUpperCase() === POST_METHOD) {
Ember.Logger.warn('hit POST Method');
promiseFunctionWrapper = () => {
return this.get('ajax').post(endPoint, {data: data, headers: headers});
};
}
return RSVP.Promise.race([promiseFunctionWrapper(), this.delay(timeoutInMillisecs).then(() => {
if (!this.get('didAnybodyWin')) {
this.set('didAnybodyWin', true);
Ember.Logger.error('timeout of %s happened when calling the endpoint %s', backendCallTimeoutInMilliseconds, endPoint);
this.transitionToErrorPage();
return;
}
})]).catch((error) => {
if (!this.get('didAnybodyWin')) {
if (isTimeoutError(error)) {
this.set('didAnybodyWin', true);
Ember.Logger.warn('callBackEndService: isTimeoutError(error) condition is true');
this.transitionToErrorPage();
return;
} else if (isAjaxError(error) && !isInvalidError(error)) { //handles all errors except http 422 (inValid request)
this.set('didAnybodyWin', true);
Ember.Logger.warn('callBackEndService: isAjaxError(error) && !isInvalidError(error) [[ non timeout error]] condition is true');
this.transitionToErrorPage();
return;
} else {
throw error; // to be caught by the caller
}
}
});
},
The callEndpoint does a RSVP.Promise.race call to make sure the called backend API comes back before a timeout happens. It runs two promises and whichever resolves first is the one that wins. didAnybodyWin is the flag that guards both the promises from getting executed.
Up to this part is all fine.
But this didAnybodyWin becomes the shared state of this call-backend-service because it has to convey back to the caller whether it ran the default set of then or catch blocks or does it expect the caller to run its then/catch blocks.
The problem is when model() hook is run, I am doing
RSVP.all {
backendAPICall1(),
backendAPICall2(),
backendAPICAll3()
}
This RSVP.all is going to execute all 3 calls one after another, so they will hit the call-backend-service in an interleaved fashion and hence run the risk of stepping over each other (when it comes to the didAnybodyWin shared state).
How can this situation be avoided ?
Is there any other better way for the callee to signal to the caller whether or not its supposed to do something with the returned promise.