React - variable is undefined instead of true/false - component gets rendered first? - javascript

Alright, so what I'm trying to achieve is a function, that returns a Button with a different label depending on wether a file exists, or not. So far so good, the code itself seems to be working, but in the wrong order.
What i did is print out 'file does exist' or 'does not exist' in the part of the function that does the actual checking, saving a boolean to a test variable, using said variable to determine which button gets returned in the end.
Now what happens it that, even if the first part prints out 'file does exist' (which should save true to test), console.log(test)a bit further down returns undefined which, of course, results in the conditional not to work.
I am sure I'm overlooking something very simple but I just can't figure it out.
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import fs from 'fs';
var ConfigChecker = React.createClass({
render: function(){
var test;
fs.open('./App.js', 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('file does not exist');
test = false;
}
throw err;
}
console.log('this should be executed first:');
console.log('file does exist')
test = true;
});
if (test)
{
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="true" />);
}
else {
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="false" />);
}
}
});
export default ConfigChecker;
this is what gets returned in the dev console

Async calls do not work as you think they do. Anytime you see a function which is async (expects a callback, returns a Promise) in JavaScript prefix the function's name in your mind with "registerInterestIn{functionName}". So fs.open becomes fs.registerInterestInOpen.
With that in mind, your code now looks like this:
var test;
fs.registerInterestInOpen('./App.js', 'r', ...);
// Still undefined here, we just registered an interest
// in the results of opening ./App.js ... nothing has
// actually *happened* yet.
if (test) {
} else {
}
// And when we get down here, JavaScript will actually *do*
// the opening, and call our callback with the result, when
// the operating system gets back to us with the data or an error
How to handle this issue then?
You'll need to use a bit of React state so you know which of the three states your component is in (waiting to find out if the file exists, the file exists, the file does not exist). Then you need to change your state in the callback to fs.open to tell React to re-render your component:
const ConfigChecker = React.createClass({
getInitialState() {
return { test: null };
},
componentDidMount() {
fs.open('./App.js', 'r', (err, fd) => {
if (err && err.code === 'ENOENT') {
console.log('file does not exist');
return this.setState(() => { test: false});
}
this.setState(() => { test: true});
});
},
render() {
if (this.state.test == null) {
return 'Loading ...';
} else {
return <RaisedButton label={this.state.test} />;
}
}

try this:
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import fs from 'fs';
var ConfigChecker = React.createClass({
getInitialState: function(){
return {
test: true
}
},
// or componentWillMount() should work
componentDidMount: function(){
var test;
var self = this;
fs.open('./App.js', 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('file does not exist');
self.setState({
test: false
});
// test = false;
}
throw err;
}
console.log('this should be executed first:');
console.log('file does exist')
self.setState({
test: true
});
// test = true;
});
},
render: function(){
if (this.state.test)
{
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="true" />);
}
else {
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="false" />);
}
}

Related

How do I test a class's method that has arguments using sinon.js

Hi I am using sequelize ORM with Postgres database for this node.js express.js app. As for testing I am using mocha, chai and sinon.
I am trying to complete a test for a class's method. The class instant i call it userService and the method is findOneUser .. This method has got an argument id .. So in this moment I want to test for a throw error the test works if I dont put an argument. That means this test is obviously not complete.
Here is the class method I want to test
userService.js
module.exports = class UserService {
async findOneUser(id) {
try {
const user = await User.findOne({ where: { id: id } }); // if null is returned error is thrown
if (!user) {
throw createError(404, "User not found");
}
return user;
} catch (err) {
throw err
}
}
}
And here is my test code
userService.spec.js
describe.only("findOne() throws error", () => {
let userResult;
const error = customError(404, "User not found"); // customError is a function where I am throwing both status code and message
before("before hook last it block issue withArgs" , async () => {
// mockModels I have previously mocked all the models
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).resolves(null); // basically have this called and invoked from calling the method that it is inside of based on the argument fakeId
userResult = sinon.stub(userService, "findOneUser").throws(error); // 🤔this is the class instances method I wonder how to test it withArguments anything I try not working but SEE BELOW COMMENTS🤔
});
after(() => {
sinon.reset()
});
it("userService.findOneUser throws error works but without setting arguments 🤔", () => {
expect(userResult).to.throw(error);
});
/// this one below still not working
it("call User.findOne() with incorrect parameter,, STILL PROBLEM 🤯", () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
});
But for the method of my class findOneUser has an argument (id) how can I pass that argument into it where I am stubbing it?
Or even any ideas on how to fake call the class method?? I want both it blocks to work
EDIT
I forgot to mention I have stubbed the mockModels.User already and that was done before the describe block
const UserModel = {
findByPk: sinon.stub(),
findOne: sinon.stub(),
findAll: sinon.stub(),
create: sinon.stub(),
destroy: sinon.stub()
}
const mockModels = makeMockModels( { UserModel } );
// delete I am only renaming UserModel to User to type things quicker and easier
delete Object.assign(mockModels, {['User']: mockModels['UserModel'] })['UserModel']
const UserService = proxyquire(servicePath, {
"../models": mockModels
});
const userService = new UserService();
const fakeUser = { update: sinon.stub() }
SOLUTION
I think this might be the solution to my problem strange but it works the tests is working with this
describe.only("findOne() throws error", async () => {
const errors = customError(404, "User not found"); // correct throw
const errors1 = customError(404, "User not foundss"); // on purpose to see if the test fails if should.throw(errors1) is placed instead of should.throw(errors)
after(() => {
sinon.reset()
});
// made it work
it("call User.findOne() with incorrect parameter, and throws an error, works some how! 🤯", async () => {
userResult = sinon.spy(userService.findOneUser);
try {
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
await userResult(fakeId);
} catch(e) {
// pass without having catch the test fails 😵‍💫
} finally {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
}
});
it("throws error user does not exist,,, WORKS", () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw
mockModels.User.findOne.withArgs(fakeId).should.throw(errors); // specially this part without having the catch test fails. but now test works even tested with errors1 variable
expect(userResult).to.throw;
});
});
MORE CLEANER SOLUTION BELOW
I like this solution more as I call the method inside within the describe block, then I do the test of the two it blocks.
The problem was I should not have stubbed the class method but should have called it directly, or used sinon.spy on the method and call it through the spy. As for checking that the errors are as expected then running this line of expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors); was the solution I needed.
describe.only('findOne() user does not exists, most cleanest throw solution', () => {
after(() => {
sinon.reset()
});
const errors = customError(404, "User not found");
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
userResult = sinon.spy(userService, "findOneUser"); // either invoke the through sinon.spy or invoke the method directly doesnt really matter
userResult(fakeId);
// userResult = userService.findOneUser(fakeId); // invoke the method directly or invoke through sinon.spy from above
it('call User.findOne() with invalid parameter is called', () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
it('test to throw the error', () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors);
expect(userResult).to.throw;
})
});

How to test a recursive function is being called X amount of times using Jest? My method hangs forever if I use the spy method?

utils file
const isStatusError = (err: any): err is StatusError =>
err.status !== undefined;
export const handleError = async (err: any, emailer?: Mailer) => {
const sendErrorEmail = async (
subject: string,
text: string,
emailer?: Mailer
) => {
try {
const mail: Pick<Mail, "from" | "to"> = {
from: config.email.user,
to: config.email.user,
};
// 2. This throws an error
await emailer?.send({ ...mail, subject, text });
} catch (err) {
// 3. It should call this function recursively...
await handleError(new EmailError(err), emailer);
}
};
if (isStatusError(err)) {
if (err instanceof ScrapeError) {
console.log("Failed to scrape the website: \n", err.message);
}
if (err instanceof AgendaJobError) {
console.log("Job ", err.message);
// #TODO
}
if (err instanceof RepositoryError) {
console.log("Repository: ");
console.log(err.message);
// #TODO
}
// 4. and eventually come here and end the test...
if (err instanceof EmailError) {
console.log("Failed to create email service", err);
}
// 1. It goes here first.
if (err instanceof StatusError) {
console.log("generic error", err);
await sendErrorEmail("Error", "", emailer);
}
} else {
if (err instanceof Error) {
console.log("Generic error", err.message);
}
console.log("Generic error", err);
}
};
test file
import * as utils from "./app.utils";
import { Mailer } from "./services/email/Emailer.types";
import { StatusError } from "./shared/errors";
const getMockEmailer = (implementation?: Partial<Mailer>) =>
jest.fn<Mailer, []>(() => ({
service: "gmail",
port: 5432,
secure: false,
auth: {
user: "user",
pass: "pass",
},
verify: async () => true,
send: async () => true,
...implementation,
}))();
describe("error handling", () => {
it("should handle email failed to send", async () => {
const mockEmailer = getMockEmailer({
send: async () => {
throw new Error();
},
});
// This line is the problem. If I comment it out, it's all good.
const spiedHandleError = jest.spyOn(utils, "handleError");
// #TODO: Typescript will complain mockEmailer is missing a private JS Class variable (e.g. #transporter) if you remove `as any`.
await utils.handleError(new StatusError(500, ""), mockEmailer as any);
expect(spiedHandleError).toBeCalledTimes(2);
});
});
This test runs forever, and it is because I made handleError a spy function.
I tried to import itself and run await utils.handleError(new EmailError(err), emailer) but it still continue to hang.
So what happens is:
It throws an Error.
It will then figure out it is a StatusError which is a custom error, and it will output the error and call a function to send an email.
However, attempting to send an email throws another Error
It should then call itself with EmailError
It will detect it is an EmailError and only output the error.
Logic wise, there is no infinite loop.
In the utils file, if you comment this const spiedHandleError = jest.spyOn(utils, "handleError"); out, the test will be fine.
Is there a way around this somehow?
I realized it's my own logic that caused the infinite loop. I forgot to add the return statement to each of my if statement.
My spy function now works.
const spiedHandleError = jest.spyOn(utils, "handleError");
await utils.handleError({
err: new StatusError(500, "error"),
emailer: mockEmailer,
});
expect(spiedHandleError).toBeCalledTimes(2);
expect(spiedHandleError.mock.calls).toEqual([
[{ err: new StatusError(500, "error"), emailer: mockEmailer }],
[
{
err: new EmailError("failed to send an error report email."),
emailer: mockEmailer,
},
],
]);
It's impossible to spy or mock a function that is used in the same module it was defined. This is the limitation of JavaScript, a variable cannot be reached from another scope. This is what happens:
let moduleObj = (() => {
let foo = () => 'foo';
let bar = () => foo();
return { foo, bar };
})();
moduleObj.foo = () => 'fake foo';
moduleObj.foo() // 'fake foo'
moduleObj.bar() // 'foo'
The only way a function can be written to allow this defining and consistently using it as a method on some object like CommonJS exports:
exports.handleError = async (...) => {
...
exports.handleError(...);
...
};
This workaround is impractical and incompatible with ES modules. Unless you do that, it's impossible to spy on recursively called function like handleError. There's babel-plugin-rewire hack that allows to do this but it's known to be incompatible with Jest.
A proper testing strategy is to not assert that the function called itself (such assertions may be useful for debugging but nothing more) but assert effects that the recursion causes. In this case this includes console.log calls.
There are no reasons for spyOn to cause infinite loop. With no mock implementation provided, it's just a wrapper around original function. And as explained above, there's no way how it can affect internal handleError calls, so it shouldn't affect the way tested function works.
It's unsafe to spy on utils ES module object because it's read-only by specification and can result in error depending on Jest setup.

Javascript testing - function called with specfic argument

I am trying to write a unit test for a function but cannot figure out how to check if it makes a call to a nested function with a specific argument. I am assuming I will need to use sinon alongside chai and mocha for this, but I could really use some help.
The function I would like to test looks like:
function myFunc(next, value) {
if (value === 1) {
const err = new Error('This sets an error');
next(err);
} else {
next();
}
}
I would like to test if next is called with or without the err variable. From what I read so far I should use a spy for this (I think) but how would I use that spy? Looking at this example from the Sinon docs it is unclear to me where PubSub comes from:
"test should call subscribers with message as first argument" : function () {
var message = "an example message";
var spy = sinon.spy();
PubSub.subscribe(message, spy);
PubSub.publishSync(message, "some payload");
sinon.assert.calledOnce(spy);
sinon.assert.calledWith(spy, message);
}
Source: https://sinonjs.org/releases/latest/assertions/
If you have a function like this
function myFunc(next, value) {
if (value === 1) {
const err = new Error('This sets an error');
next(err);
} else {
next();
}
}
The test could look like this
it ('should call the callback with an Error argument', function (done) {
const callback = (err) => {
if (err && err instanceof Error && err.message === 'This sets an error'){
// test passed, called with an Error arg
done();
} else {
// force fail the test, the `err` is not what we expect it to be
done(new Error('Assertion failed'));
}
}
// with second arg equal to `1`, it should call `callback` with an Error
myFunc(callback, 1);
});
so you don't necessarily need sinon for that

Jest: .toThrow matcher does not pass test on Error

I want to test invalid Inputs of a function and expect the function to throw
on that inputs. However the test does not pass but the function still throws the error. Im kind of a beginner with jest so I dont know why that happens.
My function looks like this:
export class MyClass{
static theFunction(tokens){
let result = [];
if (typeof tokens[0] === "string") {
return tokens;
} else {
try {
for(let token of tokens){
result.push(token.text);
}
return result;
} catch (e) {
throw new Error(e); //also tried throw e; and no try/catch aswell
}
}
}
}}
Test.js:
import {MyClass} from './MyClass'
describe('Test the MyClass:', () => {
test('invalid inputs for thefunction()', () => {
expect(MyClass.theFunction(0)).toThrow(/*'TypeError: tokens is not iterable'*/);
//Tried with and without the Error Message
});
});
What am I missing?
Wrap your function in an anonymous function so that Jest can catch the error:
describe('Test the MyClass:', () => {
test('invalid inputs for thefunction()', () => {
expect( () => { MyClass.theFunction(0) } ).toThrow();
});
});
You might want to read the section about toThrow() in the Jest documentation and also check the implementation in the Jest project on Github.

testing promises causing undefined values

I am getting this error when I am testing my code:
1) Sourcerer Testing: getStatusCode :
Error: Expected undefined to equal 200
I'm not sure why I am getting undefined in my tests but when I run the code I get 200. It might be from not handling promises properly
Test code:
import expect from 'expect';
import rp from 'request-promise';
import Sourcerer from './sourcerer';
describe("Sourcerer Testing: ", () => {
let sourcerer = new Sourcerer(null);
const testCases = {
"https://www.google.com": 200,
// "www.google.com":
};
describe("getStatusCode", () => {
it("", () => {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
expect(sourcerer.getStatusCode()).toEqual(testCases[testCase]);
}
});
});
});
code:
import rp from 'request-promise';
export default class Sourcerer {
constructor(url) {
this.options = {
method: 'GET',
url,
resolveWithFullResponse: true
};
this.payload = {};
}
setSourcererUrl(url) {
this.url = url;
}
getSourcererUrl() {
return this.url;
}
analyzeSourcePage() {
rp(this.options).then((res) => {
console.log(res);
}).catch((err) => {
console.log("ERROR");
throw(err);
});
}
getStatusCode() {
rp(this.options).then((res) => {
console.log(res.statusCode);
return res.statusCode;
}).catch((err) => {
console.log("STATUS CODE ERROR");
return 0;
});
}
}
getStatusCode doesn't return anything. And it should return a promise:
getStatusCode() {
return rp(this.options)...
}
The spec will fail in this case, because it expects promise object to equal 200.
It is even more complicated because the spec is async and there are several promises that should be waited before the spec will be completed. It should be something like
it("", () => {
let promises = [];
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
let statusCodePromise = sourcerer.getStatusCode()
.then((statusCode) => {
expect(sourcerer.url).toEqual(testCase);
expect(statusCode).toEqual(testCases[testCase]);
})
.catch((err) => {
throw err;
});
promises.push(statusCodePromise);
}
return promises;
});
co offers an awesome alternative to Promise.all for flow control:
it("", co.wrap(function* () {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
let statusCode = yield sourcerer.getStatusCode();
expect(statusCode).toEqual(testCases[testCase]);
}
});
Disclaimer: I wouldn't run a for-loop in a single it(), since I want to know which iteration failed. granted that there are ways to achieve that, but that is another story. Also, this very much depends on you test runner, but here is some rules of thumb I find useful.
But for what you have asked, the test should not evaluate until the promise is resolved. sometimes (e.g. in mocha), that means returning the promise from the it() internal function. sometimes, it means getting a done function and calling it when you are ready for the test to evaluate. If you provide more info on your test framework, I may be able to help (others certainly would be)

Categories

Resources