Expect on value assigned from promise (Jest unittest) - javascript

I'm unittesting a es6 class and want my test to verify the values written to the class variables.
My class has the folowing method:
export default class ctrl{
constructor(){}
postClosingNote(payload) {
this._headerNoteService.createNewNote(payload).then(data => {
this.note = {};
this.claimNotes = data;
this.returnToClaimHeader();
});
}
}
Service method:
createNewNote(postData){
return this._http.post(`${api}`, postData).then(res => res.data);
}
Unittest:
beforeEach(() => {
when(headerNoteService.createNewNote).calledWith(newNote).mockReturnValue(Promise.resolve({'claimNotes': 'txt'}));
});
const newNote = {
text: "txt",
claimDescriptionTypeId: 4,
claimHeaderId: headerId
};
test('Confirm that newly submitted note is added to the headers notes', () => {
target = new ctrl();
target.postClosingNote(newNote);
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
});
Output from running test:
Expected value to equal:
{"claimNotes": "txt"}
Received:
undefined
logging target to console does not include any reference to this.note or this.claimNotes

I believe this is happening because postClosingNote() returns immediately before the promise is resolved. You need to wait for it to resolve before testing the result. You can return the promise from postClosingNote and wait it to resolve in your test.
(haven't actually run this code, there might be syntax errors but you get the idea):
postClosingNote(payload) {
return this._headerNoteService.createNewNote(payload).then(data => {
this.note = {};
this.claimNotes = data;
this.returnToClaimHeader();
});
}
Unittest:
test('Confirm that newly submitted note is added to the headers notes', () => {
target = new ctrl();
target.postClosingNote(newNote).then(result => {
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
}
});

Using async/await worked:
test('Confirm that newly submitted note is added to the headers notes', async () => {
target = new ctrl();
await target.postClosingNote(newNote);
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
});

Related

How do I properly return 'this' in Thenable objects?

I am developing a NodeJS package that contains a Thenable class (see Thenable objects) that is supposed to return this (the initialized object itself), but instead it is returning a new, uninitialized object and it is stuck in a neverending loop. Below is the code:
node_modules/<my package>/index.js
const axios = require('axios');
const config = require('./config.json');
class MyAPI {
#token = "";
constructor(username, password) {
this.#token = token;
}
then(resolve, reject) {
const options = {
url: "/api/authenticate",
baseURL: "https://myapi.com/",
method: 'post',
maxRedirects: 0,
data: {
token: this.#token
},
transformRequest: this.#transformRequest('urlencoded'),
transformResponse: this.#transformResponse
};
axios.request(options).then((res) => {
if (res.data.authenticated === true) {
console.log("Authenticated!");
resolve(this); // <-- should return the initialized object, but instead returns a new uninitialized object
} else {
reject(new Error('Invalid credentials'));
}
});
}
#transformRequest(type) {
if (type === 'urlencoded') {
return function(data) {
return (new URLSearchParams(data)).toString();
}
} else if (type === 'json') {
return function(data) {
return JSON.stringify(data);
}
}
}
#transformResponse(data) {
return JSON.parse(data);
}
getSomething() {
// Gets something from remote API
return response;
}
}
module.exports.MyAPI = MyAPI;
index.js
const { MyAPI } = require('<my package>');
(async function(){
try {
const app = await new MyAPI(config.auth.token);
console.log("App initialized!"); // Code never reaches this command
console.log(app.getSomething());
} catch (e) {...} // Handle the error
})()
The log gets filled up with "Authenticated!" and the code never makes it to console.log("App initialized!"). I've seen this answer but it does not help me because I know this is referring correctly to the object.
Replacing resolve(this) with resolve() stops this, but await new MyAPI() resolves to undefined and I cannot later run app.getSomething().
In a non-Thenable class (e.g. new MyClass()), it resolves to the object itself, so further operations can be done on it, so why can't await new MyAPI() also resolve to the object itself like a constructor should?
Just don't do that. Instead of making your objects thenable, give them an init method that simply returns a promise. Then use them as
const app = new MyAPI(config.auth.token);
await app.init();
console.log("App initialized!");

Fetching data and display it as a dropdown menu

I'm working with a Grafana plugin API and I'm trying to create a function that returns an object literal var literals that will contain new data that I fetched from the server.
The result of this function return literals; will go, as a parameter, in to another Grafana API function later.
I'm very confused by how promises work and I don't really know how to tackle this problem.
Right now in this code I'm having a problem with a console error
TypeError: data.dbs is undefined'
at the line data.dbs.forEach(element => {
I thought that after calling the .then() function the data should already be returned and yet it's still unresolved?
I would also like to know how exactly can I create this whole getDropdown() function so that it will be able to send the resolved data forward?
export function getDropdown() {
var literals = {
path: 'displayMode',
name: 'Cell display mode',
description: 'Color text, background, show as gauge, etc',
settings: {
options: [{
value: 1,
label: 'Placeholder'
}],
},
};
var data = {
dbs: [{
id: 1,
database: 'placeholder',
}, ],
};
fetch('/api/datasources')
.then(function(res) {
return res.json();
})
.then(json => {
data = json;
var counter = 0;
data.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});
return literals;
}
I was able to solve the problem with this code
fetch('/api/datasources')
.then(data => {
return data.json();
})
.then(data => {
data.forEach((element: { database: string; id: number }) => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});
I think there was something wrong with data.dbs but I'm still not sure what.
Insted of using then() functions, try using async/await where you can asyncronously execute functions & you can also wait till getting a promise.
In the above code you can write an async function & await till you get response from
fetch('/api/datasources')
// Do
async function getData() {
var data = await fetch('/api/datasources')
var jsonData = JSON.parse(data)
jsonData.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
}
// Instead of
fetch('/api/datasources')
.then(function(res) {
return res.json();
})
.then(json => {
data = json;
var counter = 0;
data.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});

Sinon sequelize stub - Modified object not returned with Promise.resolve()

I am mocking a Sequelize find call, and then modifying the result before returning the object in a Promise.resolve();
Here's my simplified code;
test.js
test('can pass test', t => {
let mock_instance = models.myModel.build({
a_property: 5
});
let Stub = sandbox.stub(models.myModel, 'find', () => {
return Promise.resolve(mock_instance);
});
models.myModel.functionCall(mock_instance.id).then(returned_object => {
let expected = {
a_property: 6
};
t.equal(returned_object.get({plain:true}), expected);
// Expected { a_property: 6 }
// Actual { a_property: 5 }
Stub.restore();
t.end();
});
})
model.js // Class method
classFunction: (id) => {
return new Promise((resolve, reject) => {
return sequelize.models.myModel.find({
where: {
id: id
}
}).then(mymodel => {
mymodel.a_property++;
resolve(mymodel);
}).catch(err => {
reject(err);
});
});
Why is this happening? I assume it's something to do with Sinon intercepting the resolve thinking it's the result of the find() call, but I'm not certain and I don't know how to fix it.
Is this an anti-pattern? Is there a better way?
As I understand it, mymodel.a_property++ won't change the value in the underlying dataValues object (you'd need to call set to do this). When you call returned_object.get({plain:true}) later it rebuilds the object from the underlying values.
Also, your "classFunction" can be a lot simpler. It doesn't need to wrap its result in an extra Promise:
classFunction: (id) => {
return sequelize.models.myModel.find({
where: { id: id }
}).then(mymodel => {
mymodel.set('a_property', mymodel.a_property + 1);
return mymodel;
});
};

Returning error as an array object value with promise

I'm using a scraping function to get some data from a bunch of urls listed inside an array. Here is the following function :
function getNbShares(urls) {
return Promise.map(urls, request).map((htmlOnePage, index) => {
const $ = cheerio.load(htmlOnePage),
share = $('.nb-shares').html();
return {
url: urls[index],
value: share
};
}).catch(function (urls, err) {
return {
url: urls[index],
value: err
};
});
}
It's working fine, however the error handling isn't. What I would like is that when I have an error (either because the page doesn't load or if the DOM selector is wrong) the map function/request keep doing is job and it returns me the error (or null) as a value attached to the url in the final array object.
I think you just want to do that handling a bit earlier, within the mapping function; and I think you can avoid having two separate mapping operations; see comments:
function getNbShares(urls) {
return Promise.map(
urls,
url => request(url)
.then(htmlOnePage => { // Success, so we parse
const $ = cheerio.load(htmlOnePage), // the result and return
value = $('.nb-shares').html(); // it as an object with
return { url, value }; // `url` and `value` props
})
.catch(error => ({url, error})) // Error, so we return an
// object with `url` and
// `error` props
}
);
}
(I've assumed you're using ES2015+, as you were using arrow functions.)
I might opt to factor part of that out:
function getNbSharesFromHTML(html) {
const $ = cheerio.load(html);
return $('.nb-shares').html();
}
function getNbShares(urls) {
return Promise.map(
urls,
url => request(url)
.then(htmlOnePage => ({url, value: getNbSharesFromHTML(htmlOnePage)))
.catch(error => ({url, error}))
}
);
}
Possibly even smaller pieces:
function getNbSharesFromHTML(html) {
const $ = cheerio.load(html);
return $('.nb-shares').html();
}
function getNbSharesFromURL(url) {
return request(url)
.then(htmlOnePage => ({url, value: getNbSharesFromHTML(htmlOnePage)))
.catch(error => ({url, error}));
}
function getNbShares(urls) {
return Promise.map(urls, getNbSharesFromURL);
}

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