Unit testing redux-saga select - mocha.js

I have a simple saga of this form:
const getAccountDetails = function * () {
const { url } = yield select(state => state.appConfig)
const accountDetails = yield call(apiFetchAccountDetails, url)
}
I'm trying to write a unit test:
describe('getAccountDetails', () => {
const iterator = getAccountDetails()
it("should yield an Effect 'select(state=> state.appConfig)'", () => {
const effect = iterator.next().value
const expected = select(state => state.appConfig)
expect(effect).to.deep.eql(expected)
})
This test fails. Although effect and expected are very similar, they are not identical.
At least one of the differences is buried in payload.selector.scopes, where the yielded effect and expected are as follows:
As the scopes of these two will always be different, how can these tests ever be made to work?
eta: this pattern is adapted from the example linked to from the redux-saga docs

Cracked it after finding this issue from way back.
The fix is to create a named function to do the select and export it from the module where the saga under test lives, and then use this same function in tests. All is well.
export const selectAppConfig = state => state.appConfig
const getAccountDetails = function * () {
const { url } = yield select(selectAppConfig)
const accountDetails = yield call(apiFetchAccountDetails, url)
}
import {selectAppConfig} from './sagaToTest'
describe('getAccountDetails', () => {
const iterator = getAccountDetails()
it("should yield an Effect 'select(state=> state.appConfig)'", () => {
const effect = iterator.next().value
const expected = select(selectAppConfig)
expect(effect).to.deep.eql(expected)
})

Related

Is having more than one API fetch request in a React useEffect hook always very slow?

I am writing a simple application using React to fetch and display data from the Star Wars API. I first fetch information about a particular planet. The response JSON for a given planet contains a bunch of data, including an array of URLs pointing to further data about notable residents of said planet. I next call each of those URLs in order to display a list of the names of the residents of the current planet.
This code works, but is slow as heck:
const url = `https://swapi.dev/api/planets/`;
const [currentPlanetNumber, setCurrentPlanetNumber] = React.useState(1);
const [currentPlanet, setCurrentPlanet] = React.useState({});
const [currentPlanetResidentsDetails, setCurrentPlanetResidentsDetails] =
React.useState([]);
React.useEffect(() => {
(async () => {
const planetData = await fetch(`${url}${currentPlanetNumber}/`).then(
(response) => response.json()
);
setCurrentPlanet(planetData);
if (planetData.residents.length === 0) {
setCurrentPlanetResidentsDetails(["No notable residents"]);
} else {
const residentsURLs = planetData.residents;
const residentsNames = await Promise.all(
residentsURLs.map(async (item) => {
const name = await fetch(item).then((response) => response.json());
const newName = name.name;
return newName;
})
);
setCurrentPlanetResidentsDetails(residentsNames);
}
})();
}, [currentPlanetNumber]);
The following code works fairly fast for this:
const url = `https://swapi.dev/api/planets/`;
const [currentPlanetNumber, setCurrentPlanetNumber] = React.useState(1);
const [currentPlanet, setCurrentPlanet] = React.useState({});
const [currentPlanetResidentsDetails, setCurrentPlanetResidentsDetails] =
React.useState([]);
React.useEffect(() => {
(async () => {
const planetData = await fetch(`${url}${currentPlanetNumber}/`).then(
(response) => response.json()
);
setCurrentPlanet(planetData);
})();
}, [currentPlanetNumber]);
React.useEffect(() => {
(async () => {
if (currentPlanet.residents.length === 0) {
setCurrentPlanetResidentsDetails(["No notable residents"]);
} else {
const residentsURLs = currentPlanet.residents;
const residentsNames = await Promise.all(
residentsURLs.map(async (item) => {
const name = await fetch(item).then((response) => response.json());
const newName = name.name;
return newName;
})
);
setCurrentPlanetResidentsDetails(residentsNames);
}
})();
}, [currentPlanet]);
What makes the second one so much faster? I assumed that they would both take about the same length of time, because the same number of fetch requests get done either way.
Is it a good rule of thumb to not have more than one fetch request an any given useEffect hook?
No, there is no rule of thumb stating not to have more than one fetch request in a given useEffect.
In your first example, the fetch requests are fired consecutively, while in the second example they are fired concurrently.
Your first example seems to be more appropriate than the second. In the second example, it seems to you that the code is executing faster because both effects are firing concurrently when the component mounts. On subsequent changes to 'currentPlanetNumber', both examples should execute in the same amount of time.

Comparing results of function call before and after page actions

I have a complicated selection that I've put into a function to keep the test clean. I want to call the function before and after some page actions and compare the results.
This is my code, problem is I'm not getting the result back even though the value is extracted successfully inside the function.
const getVals = () => {
// simplified
cy.get('[id="22"] span')
.then($els => {
const vals = [...$els].map(el => el.innerText)
return vals
})
}
const vals1 = getVals()
// perform action on the page
const vals2 = getVals()
// compare
expect(vals1).to.deep.eq(vals2)
The function has a return inside .then() but it's not returning the result from the main body of the function.
Since the commands are asynchronous, I recommend changing to a custom command and adding alias to preserve the result during the intermediate actions
Cypress.Commands.add('getVals', () => {
cy.get('[id="22"] span')
.then($els => {
const vals = [...$els].map(el => el.innerText)
return vals // vals is Cypress "subject"
})
}
cy.getVals().as('vals1')
// perform action on the page
cy.getVals().then(vals2 => { // use then to obtain vals
cy.get('#vals1').then(vals1 => { // retrieve first from alias
// compare
expect(vals1).to.deep.eq(vals2)
})
})

how do I unit test this function which handles observables

I have created this function because for all the requests my application sends out using http.post, this is how different parts handle the response. So rather than duplicating the code, I thought to create a function. But I am unable to figure out how to unit test this function.
private editAnswerSubject: Subject<Result>;
subscribeToReturnedObservable(observable:Observable<any>, subject:Subject<Result>) {
observable.subscribe((res) => {
const ev = <HttpEvent<any>>(res);
if (ev.type === HttpEventType.Response) {
const isResponseStructureOK: boolean = this.helper.validateServerResponseStructure(ev.body);
if (isResponseStructureOK) {
const response: ServerResponseAPI = ev.body;
subject.next(new Result(response.result, response['additional-info']));
} else {
subject.next(new Result(messages.error, messages.invalidStructureOfResponse));
}
}
},
(error: ServerResponseAPI) => {
const errorMessage: string = this.helper.userFriendlyErrorMessage(error);
subject.next(new Result(messages.error, errorMessage));
},
() => { // observable complete
});
}
editAnswer(answer: Answer): any {
const observable = this.bs.editAnswer(answer)
this.subscribeToReturnedObservable(observable,this.editAnswerSubject);
}
The test I have written so far is
describe('subscribeToReturnedObservable tests:', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [QuestionManagementService, HelperService, WebToBackendInterfaceService, AuthService, HttpClient, HttpHandler]
});
});
fit('should call send next value for the subject is the response from the server is ok', () => {
const questionService:QuestionManagementService = TestBed.get(QuestionManagementService);
const body = {"result":"success", "additional-info":"some additional info"};
const receivedHttpEvent = new HttpResponse({body:body});
let observable = new Observable();
spyOn(observable,'subscribe').and.returnValue(receivedHttpEvent);
spyOn(questionService['editQuestionSubject'],'next');
questionService.subscribeToReturnedObservable(observable,questionService['editQuestionSubject']);
observable.subscribe();
expect(questionService['editQuestionSubject'].next).toHaveBeenCalled();
});
});
But it get error Expected spy next to have been called.
I did this (hoping that it is the right way). The scope of testing is to check that the Subject's next is called correctly. So create an Observable using of and let the code flow from there.
fit('should call send next value for the subject is the response from the server is ok', () => {
const questionService:QuestionManagementService = TestBed.get(QuestionManagementService);
const helperService:HelperService = TestBed.get(HelperService);
const body = {"result":"success", "additional-info":"some additional info"};
const receivedHttpEvent = new HttpResponse({body:body});
const expectedResult = new Result('success', 'some additional info');
spyOn(helperService,'validateServerResponseStructure').and.returnValue(true);
let observable = of(receivedHttpEvent);
spyOn(questionService['editQuestionSubject'],'next');
questionService.subscribeToReturnedObservable(observable,questionService['editQuestionSubject']);
expect(questionService['editQuestionSubject'].next).toHaveBeenCalledWith(expectedResult);
});

How to use sinon to mock a series of knex call

Given a myknex.js:
export default { return require('knex')({client:'mysql',connection:{//conn}})}
and I want to write a unit test for the following function:
async function get({ userId }) {
return await myknex('users')
.where({ id: userId })
.returning('*');
}
The unit test looks like:
const sinon = require('sinon');
const sandbox = sinon.createSandbox();
const { myknex } = require('./myknex');
it('no record', async () => {
sandbox
.stub(myknex('users'), 'executeQuery').resolves([]);
const result = await myrepo.get({ userId: 1 });
const expectedResult = [];
expect(result).to.deep.equal(expectedResult);
});
I got an error message about:
TypeError: Cannot stub non-existent own property executeQuery
How can I mock the chained myknex calls?
Since you have myknex.js export a function of knex, we need to use proxyquire to mock this in test file.
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const proxyquire = require('proxyquire'); // include proxyquire
const expectedResult = [];
const knexQuery = {
where: sinon.stub().returnsThis(), // because we want to call `returning` afterwards
returning: sinon.stub().resolves(expectedResult) // resolve it as promise
}
const myknexStub = sinon.stub().returns(knexQuery);
const myrepo = proxyquire('./src', { './myknex': myknexStub }); // your source file and stub myknex here
describe(('Test My Module'), () => {
it('no record', async () => {
const result = await myrepo.get({ userId: 1 });
// many options to unit test the function
expect(myknexStub.calledWith('users')).to.be.ok;
expect(knexQuery.where.calledWith({ id: 1 })).to.be.ok;
expect(knexQuery.returning.calledWith('*')).to.be.ok;
expect(knexQuery.returning.calledAfter(knexQuery.where)).to.be.ok;
expect(result).to.deep.equal(expectedResult);
});
});
Hope it helps

chai-as-promised should.eventually.equal not passing

I am trying to write a minimum working example of chai-as-promised in order to understand how it is working when testing functions that return a promise.
I have the following function:
simple.test = async (input) => {
return input;
};
and the following test function:
chai.use(sinonChai);
chai.use(chaiAsPromised);
const { expect } = chai;
const should = chai.should();
describe('#Test', () => {
it('test', () => {
expect(simple.test(1)).should.eventually.equal(1);
});
});
However, testing this results in the test not passing, but in a very long error, which is pasted here: https://pastebin.com/fppecStx
Question: Is there something wrong about the code, or what seems to be the problem here?
First: Your mixing expect and should. If you want to use should for assertion, you don't need expect.
Second: To tell mocha that a test is async you have to either call done, return a Promise or use async/await.
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const sinonChai = require('sinon-chai');
const should = chai.should();
chai.use(sinonChai);
chai.use(chaiAsPromised);
// Function to test
const simple = {
test: async (input) => {
return input;
}
}
// Test
describe('#Test', () => {
it('test', () => {
return simple.test(1).should.eventually.equal(1);
});
});

Resources