How to test redux saga using jest? - testing

I'm noob at front-end dev, but try to write tests for project.
Also, I'm using redux-saga and jest for testing
btw, I have saga:
export function* setAuthorizeCode(action) {
try {
let response = yield call(Api.token.getToken, action.payload.authorizeCode);
yield put(appActions.setAccessToken(response.data.access_token));
yield put(appActions.setIdToken(response.data.id_token));
const accessToken = yield select((state) => appSelectors.getAccessToken(state.app));
const idToken = yield select((state) => appSelectors.getIdToken(state.app));
response = yield call(Api.user.getUser, accessToken, idToken);
yield put(appActions.setUser(response.data));
} catch (error) {
console.log('Error: ', error);
}
}
function* setAuhtorizeCodeFlow () {
yield takeLatest(appActions.SET_AUTHORIZE_CODE, setAuthorizeCode);
}
export default function* authentication() {
yield all([
setAuhtorizeCodeFlow()
]);
}
And I'm write this test for this:
test('TESTING SAGAS', function(t) {
const gen = setAuthorizeCode()
let next = gen.next(appActions.setAuthorizeCode())
t.deepEqual(next.value, call(Api.token.getToken), 'must yield api.getToken')
next = gen.next(accessToken)
t.deepEqual(next.value, put(appActions.setAccessToken(accessToken)), 'must yield action appActions.setAccessToken')
next = gen.next(idToken)
t.deepEqual(next.value, put(appActions.setIdToken(idToken)), 'must yield action appActions.setIdToken')
next = gen.next()
t.deepEqual(next.value, select(accessToken), 'must select accessToken')
next = gen.next()
t.deepEqual(next.value, select(idToken), 'must select idToken')
next = gen.next(Api.user.getUser())
t.deepEqual(next.value, call(Api.user.getUser), 'must yield api.getUser')
next = gen.next(setUser)
t.deepEqual(next.value, put(appActions.setUser(setUser)), 'must yield appActions.setUser')
t.end()
})
I really don't know what is wrong.
But it's doesn't work, please I need some help, what am I doing wrong?
There is errors:
operator: deepEqual
expected: |- { '##redux-saga/IO': true, CALL: { context: null, fn:
[Function: getToken], args: [] } }
actual: |- undefined
And other 2 are the same.

Related

Cloudflare ESI worker / TypeError: Body has already been used

I'm trying to use a CloudFlare worker to manage my backend ESI fragments but i get an error:
Uncaught (in promise) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
Uncaught (in response) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
I don't find where the body has already been used
The process is:
get a response with the parts
Transform the body by replacing parts fragments with sub Backend calls (streamTransformBody function)
return the response
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
});
const esiHeaders = {
"user-agent": "cloudflare"
}
async function handleRequest(request) {
// get cookies from the request
if(cookie = request.headers.get("Cookie")) {
esiHeaders["Cookie"] = cookie
console.log(cookie)
}
// Clone the request so that it's no longer immutable
newRequest = new Request(request)
// remove cookie from request
newRequest.headers.delete('Cookie')
// Add header to get <esi>
newRequest.headers.set("Surrogate-Capability", "abc=ESI/1.0")
console.log(newRequest.url);
const response = await fetch(newRequest);
let contentType = response.headers.get('content-type')
if (!contentType || !contentType.startsWith("text/")) {
return response
}
// Clone the response so that it's no longer immutable
const newResponse = new Response(response.body, response);
let { readable, writable } = new TransformStream()
streamTransformBody(newResponse.body, writable)
newResponse.headers.append('x-workers-hello', 'Hello from
Cloudflare Workers');
return newResponse;
}
async function streamTransformBody(readable, writable) {
const startTag = "<".charCodeAt(0);
const endTag = ">".charCodeAt(0);
let reader = readable.getReader();
let writer = writable.getWriter();
let templateChunks = null;
while (true) {
let { done, value } = await reader.read();
if (done) break;
while (value.byteLength > 0) {
if (templateChunks) {
let end = value.indexOf(endTag);
if (end === -1) {
templateChunks.push(value);
break;
} else {
templateChunks.push(value.subarray(0, end));
await writer.write(await translate(templateChunks));
templateChunks = null;
value = value.subarray(end + 1);
}
}
let start = value.indexOf(startTag);
if (start === -1) {
await writer.write(value);
break;
} else {
await writer.write(value.subarray(0, start));
value = value.subarray(start + 1);
templateChunks = [];
}
}
}
await writer.close();
}
async function translate(chunks) {
const decoder = new TextDecoder();
let templateKey = chunks.reduce(
(accumulator, chunk) =>
accumulator + decoder.decode(chunk, { stream: true }),
""
);
templateKey += decoder.decode();
return handleTemplate(new TextEncoder(), templateKey);
}
async function handleTemplate(encoder, templateKey) {
const linkRegex = /(esi:include.*src="(.*?)".*\/)/gm
let result = linkRegex.exec(templateKey);
let esi
if (!result) {
return encoder.encode(`<${templateKey}>`);
}
if (result[2]) {
esi = await subRequests(result[2]);
}
return encoder.encode(
`${esi}`
);
}
async function subRequests(target){
target = esiHost + target
const init = {
method: 'GET',
headers: esiHeaders
}
let response = await fetch(target, init)
if (!response.ok) {
return ''
}
let text = await response.text()
return '<!--esi-->' + text + '<!--/esi-->'
}

React-native-fs : How to use readDir recursively using .map()?

I tried to get all the files and directories available in a folder using react-native-fs.
I created a function to get all the files and directories recursively in a folder, I call this function this way :
const data = await scanDir(path);
I first tried using the .map() function but my function return only some elements :
async function scanDir(pathOfDirToScan, data = {directory: [], files: []}) {
const readedFilesAndDir = await FS.readDir(pathOfDirToScan);
Object.keys(readedFilesAndDir).map(async key => {
if (readedFilesAndDir[key].isDirectory()) {
const directoryPath = pathOfDirToScan + '/' + readedFilesAndDir[key].name;
data.directory.push(directoryPath);
data = await scanDir(directoryPath, data);
} else {
data.files.push(pathOfDirToScan + '/' + readedFilesAndDir[key].name);
}
});
return data;
}
It seems my function return the data after the first time map is executed, but the function continue after that.
I then tried with a for loop and it works as intended :
async function scanDir(pathOfDirToScan, data = {directory: [], files: []}) {
const readedFilesAndDir = await FS.readDir(pathOfDirToScan);
for (let i = 0; i < readedFilesAndDir.length; i++) {
if (readedFilesAndDir[i].isDirectory()) {
const directoryPath = pathOfDirToScan + '/' + readedFilesAndDir[i].name;
data.directory.push(directoryPath);
data = await scanDir(directoryPath, data);
} else {
data.files.push(pathOfDirToScan + '/' + readedFilesAndDir[i].name);
}
}
return data;
}
What should I do to make the function properly works using .map() ?
The FS.readDir(dirpath) returns an array of objects as per docs. Object.keys(obj) is not required for iteration in that case, just readedFilesAndDir.map() will do your task.
Copy and pasted your own code with some corrections. Hope, it helps:
async function scanDir(pathOfDirToScan, data = {directory: [], files: []}) {
const readedFilesAndDir = await FS.readDir(pathOfDirToScan);
readedFilesAndDir.map(async eachItem=> {
if (eachItem.isDirectory()) {
const directoryPath = pathOfDirToScan + '/' + eachItem.name;
data.directory.push(directoryPath);
data = await scanDir(directoryPath, data);
} else {
data.files.push(pathOfDirToScan + '/' + eachItem.name);
}
});
return data;
}

TypeError: response.data is undefined

I'm having problems with promise response for a vForm PUT to UPDATE a model (backend in laravel).
The response code is 200 (OK, updated) and the model is updated, but I don't know why I'm having error with "response.data" in catch. There is no error and code in ".then()" is running correctly.
EDIT
Service Update funciton (vue) using vForm.
updateService(){
this.$Progress.start();
this.service.put('api/service/' + this.service.id)
.then( function (response) {
Toast.fire({
type: 'success',
title: response.data['Response']
});
this.$Progress.finish();
})
.catch( function (response) {
console.log(response);
Swal.fire("Error!", response.data['Response'], "warning");
this.$Progress.fail();
});
this.$events.$emit('ServiceInform');
},
Function in backend (laravel).
public function update(Request $request, Service $service)
{
$this->validate($request, [
'id_customers' => 'required|int',
'date' => 'required|date',
'id_technicians' => 'required|int',
'location' => 'required|string',
'details' => 'required|string'
]);
if ($request['id_technicians'] !== $service['id_technicians']) {
$assignated_by = Auth::user()->id;
$assigned_date = date('Y-m-d H:i:s');
} else {
$assignated_by = $service['assignated_by'];
$assigned_date = $service['assigned_date'];
}
if ($request['id_technicians'] == 0) {
$state = 'P';
} else {
$state = 'I';
}
$service->date = $request['date'];
$service->id_technicians = $request['id_technicians'];
$service->location = $request['location'];
$service->details = $request['details'];
$service->assigned_date = $assigned_date;
$service->assigned_by = $assignated_by;
$service->state = $state;
try {
$service->save();
return Response::json([
'Response' => 'Servicio actualizado.'
], 201);
} catch (Exception $e) {
return Response::json([
'Response' => 'No se actualizó el servicio.'
], 422);
}
}
This line looks problematic to me:
this.$Progress.finish();
It's trying to access this within the function passed to then. It seems unlikely that this will be referencing what you're expecting. You should be able to confirm with suitable console logging. My suspicion is that attempting to call this.$Progress.finish() will throw an error, triggering the catch.
Try using arrow functions for your then and catch callbacks instead.

How to Improve The axios.spread

The below code i use for doing multiple HTTP calls depending on the studentList.
It works well; however, I think the axios spread is not necessary
export default {
getFee (studentList: { studentId: string }[]) {
if (studentList.length < 1) {
Promise.resolve()
}
let promises = []
for (const student of studentList) {
if (!student.studentId) {
Promise.resolve()
}
var url = `${API_URL}/${student.studentId}`
promises.push(Axios.get(url))
}
return Axios.all(promises)
.then(Axios.spread((...args) => {
// customise the response here
return args
.map(response => response.data)
.map(data => {
// #ts-ignore
data.totalMark = data.markinPhysics + data.markinMaths + data.markinChemistry // total mark sum of marks in differnet discplines
return data
})
}))
.catch(error => {
switch (error.response.status) {
case 400:
console.log('student not found')
break
case 500:
console.log('error invoking')
break
default:
console.log('unknown error')
I have to do multiple network calls in Vue and I am using Axios.
I got it working by axios, all and axios.spread, but I think the code can be improved.
The logic is to do multiple calls for the student list and get the outputs back
Can anyone help?
Axios.all
as well as Promise.all accepts array of promises and returns a new Promise which is resolved whenever all of the given promises are resolved with an array with the result of each promise
e.g.
const promise1 = Promise.resolve('data1');
const promise2 = Promise.resolve('data2');
Promise.all([
promise1,
promise2,
]).then(results => {
// results is an array with 2 elements
console.log(results[0]); // data1
console.log(results[1]); // data2
});
you can use Axios.spread to to assign each result to a variable like this:
Promise.all([
promise1,
promise2,
]).then(Axios.spread(( result1, result2 ) => {
// args is an array with 2 elements
console.log(result1); // data1
console.log(result2); // data2
});
alternatively you can use ES6 Destructuring assignment:
Promise.all([
promise1,
promise2,
]).then(([ result1, result2 ]) => {
// args is an array with 2 elements
console.log(result1); // data1
console.log(result2); // data2
});
Unnecessary Promise.resolve()
Your Promise.resolve() function calls have no effect on the getFee method since you're not returning them
What would my implementation be
async function getFee(studentList) {
try {
const promises = studentList.reduce((acc, student) =>
student.studentId
? acc.concat(Axios.get(`${API_URL}/${student.studentId}`))
: acc
, []);
const responses = await Axios.all(promises);
return responses
.map(response => response.data)
.map(data => ({
// return new object
// with data's properties
// instead of assinging the new ones directly to the data
...data,
// total mark sum of marks in differnet discplines
totalMark: data.markinPhysics + data.markinMaths + data.markinChemistry,
}));
} catch (error) {
switch (error.response.status) {
case 400:
console.log("student not found");
break;
case 500:
console.log("error invoking");
break;
default:
console.log("unknown error");
}
}
}
export default {
getFee
}
Since you're only using args as an array, you could remove axios.spread.
axios.spread() might only be useful in older browsers now that ES2015 introduced its own spread operator. The main purpose of axios.spread() is to expand the result of axios.all() into an argument list, such that you could do:
axios.all(promiseArray).then(axios.spread(function(arg1, arg2, arg3) {
/*...*/
}))
instead of:
axios.all(promiseArray).then(function(args) {
var arg1 = args[0]
var arg2 = args[1]
var arg3 = args[2]
/*...*/
})
ES2015's rest operator does the inverse of axios.spread(), so when you combine them (as seen below), you end up with the result above, as if axios.spread() and the rest operator weren't even used:
axios.all(promiseArray).then(axios.spread(function(...args) {
var arg1 = args[0]
var arg2 = args[1]
var arg3 = args[2]
/*...*/
}))
// or newer syntax:
axios.all(promiseArray).then(axios.spread((...args) => {
const arg1 = args[0]
const arg2 = args[1]
const arg3 = args[2]
/*...*/
}))
To avoid promise chaining and improve readability, I think below can be used.
const [arg1, arg2] = await Promise.all(promises)

Karma unit test fails in phantomjs

After fixing alot of erros in the testing, now Karma output is this:
PhantomJS 1.9.8 (Windows 8 0.0.0) Controller: MainCtrl should attach a list of t hings to the scope FAILED
Error: Unexpected request: GET /api/marcas
No more request expected at ....
api/marcas is an endpoint i've created. code for MainCtrl:
'use strict';
angular.module('app')
.controller('MainCtrl', function ($scope, $http, $log, socket, $location, $rootScope) {
window.scope = $scope;
window.rootscope = $rootScope
$scope.awesomeThings = [];
$scope.things = ["1", "2", "3"];
$http.get('/api/things').success(function(awesomeThings) {
$scope.awesomeThings = awesomeThings;
socket.syncUpdates('thing', $scope.awesomeThings);
});
$scope.addThing = function() {
if($scope.newThing === '') {
return;
}
$http.post('/api/things', { name: $scope.newThing });
$scope.newThing = '';
};
$scope.deleteThing = function(thing) {
$http.delete('/api/things/' + thing._id);
};
$scope.$on('$destroy', function () {
socket.unsyncUpdates('thing');
});
$http.get('/api/marcas').success(function(marcas) {
$scope.marcas = marcas;
socket.syncUpdates('marcas', $scope.response);
$scope.marcasArr = [];
$scope.response.forEach(function(value) {
$scope.marcas.push(value.name);
});
$scope.marcaSel = function() {
for (i = 0; i < $scope.response.length; i++) {
if ($scope.selectedMarca == $scope.response[i].name) {
$scope.modelos = $scope.response[i].modelos;
};
};
};
});
until you didn't posted your test-code, I guess that your test doesn't includes the following code:
beforeEach(inject(function () {
httpBackend.expectGET('/api/marcas').respond(function(){
return {/*some status code*/, {/*some data*/}, {/*any headers*/}};
});
}));
if the karma-runner tries to execute your test-code, there are to get-requests to the $http-service, $http.get('/api/marcas') and $http.get('/api/things'). if one of these backend calls is not expected, karma cannot run the testcode successfully.
if you don't want to do special stuff for each but only return a default with success code for both calls, you can write so:
beforeEach(inject(function () {
httpBackend.expectGET(/api/i).respond(function(){
return {/*some status code*/, {/*some data*/}, {/*any headers*/}};
});
}));