Jest testing async code with then and catch present inside the function - javascript

I have to add unit testing to legacy code where I can make no changes inside the code hence can't refactor the code to add async/await or increase the jest version from 25.0.0
code file (sample code of actual implementation):
window.getUsers = function(){
$.get(url)
.then(data => {
data.forEach(val => {
$(".box-user .box-content").append("<h1>hello</h1>");
console.log('box users content code file',document.querySelector('.box-users > .box-content').innerHTML);
})
})
.catch(err => {})
}
test case i am trying to run
import path from 'path';
import '#testing-library/jest-dom/extend-expect';
const fs = require('fs');
const html = fs.readFileSync(path.resolve(__dirname, './code-file.html'),'utf8');
const $ = require('jquery');
const mockResponse = {
"data": [
{
"id": "test id",
"name": "test name",
"surname": "test surname",
"jobTitle": "test job title",
}
],
"length": 1
};
describe("code-file",()=>{
document.documentElement.innerHTML = html;
it("api mocking and appending html ",()=>{
$.get = jest.fn().mockImplementation(() => {
return Promise.resolve(mockResponse);
});
let {getUsers} = require("./code-file");
getUsers();
let boxUsersHtml = document.querySelector(".box-users > .box-content").innerHTML;
console.log('box users content testing file',boxUsersHtml)
})
})
as you can see inner html for testing code is empty while I do see inner html of code file to have some value. I believe this is due to the async behavior where in the test case is complete and the html has not been appended yet because it is present inside a then block.
need to know how to execute then block before any assertions(before console in the above code) are made without making changes in actual code.
done function did not help link for reference testing async code

Related

how to update name with variable in nodejs?

I have dummy template of package.json . I want to copy dummy package.json inside some folder (Application name folder) and update the name from from package.json . can we do this in node js.
here is my source package.json file
{
"name":"$name"
}
I tried like this
const fs = require('fs');
const prompt = require('prompt-sync')();
let appName = prompt('what is application name..?');
if(!appName){
appName='temp'
}
console.log(`Application name is ${appName}`);
if (!fs.existsSync(`${appName}`)){
fs.mkdirSync(`${appName}`);
}
fs.copyFile('./source/package.json', `${appName}/package.json`, (err) => {
if (err) throw err;
console.log('source.txt was copied to destination.txt');
});
when I run node index.js . it ask "application name" user enter application name let say example (abc). It create a folder abc and put package.json file which is working fine.
Now issue is I want the content of package.json is
{
"name":"abc"
}
can we replace the name variable ?
Let's say you have ./abc/package.json and its contents look like this:
{
"name": "$name",
"version": "0.1.0"
}
If you want to modify just the name field, you'll have to:
read the package data as JSON text
parse it as an object
modify the name property to your desired value
write it back to the file
A minimal example of doing that in a script might look like this:
./rename.mjs:
import {readFile, writeFile} from 'node:fs/promises';
const pkgPath = './abc/package.json';
const textEncoding = {encoding: 'utf-8'};
const json = await readFile(pkgPath, textEncoding);
const pkgData = JSON.parse(json);
pkgData.name = 'abc';
const prettyJson = JSON.stringify(pkgData, null, 2);
await writeFile(pkgPath, prettyJson, textEncoding);
After you run it:
node rename.mjs
The package contents will be updated:
{
"name": "abc",
"version": "0.1.0"
}
Ref:
fsPromises.readFile
fsPromises.writeFile

Cypress: extract value from JSON body to a variable

I am using cypress.io to test an API(Created using Node.js). I want to extract value from JSON response of the API and save it to a variable.
I have tried solutions mentioned in the below links it did not work:
Cypress - get value from json response body
Below is the code i am using to test the API:
/// <reference types="cypress" />
describe("Testing Provider Catalog API", () => {
it("Provider Catalog API GET Request", () => {
cy.request('GET', 'v1/providers')
.then((response) => {
expect(response).to.have.property('status', 200)
expect(response.body).to.not.be.null
// expect(response.body.providers).to.have.length(1)
})
cy.screenshot()
})
it("Provider Catalog API POST Request", () => {
const provider = {
"type": "loadboard",
"name": "123loadboard"
};
cy.request('POST', 'v1/providers', provider)
cy.screenshot()
})
it("Provider Catalog API PUT Request", () => {
const provider = {
"type": "loadboard",
"name": "123loadboard"
};
cy.request('PUT', 'v1/providers/61a54a66a2b734859481931c', provider)
cy.screenshot()
})
it("Provider Catalog API DELETE Request", () => {
cy.request('DELETE', 'v1/providers/61a68e7ca6011e605029191b')
cy.screenshot()
})
})
Below is the code that i am using
var providerId
cy.wait('#newObject').then((response) => {
expect(response.status).to.eq(201)
expect(response.responseBody).to.have.property('_id')
const body = (response.responseBody)
providerId = body['_id']
})
cy.get(someInput).type(newId)
Sample output of the API:
{
"_id":"61a54ba1a2b7348594819323",
"type":"loadboard",
"name":"123loadboard",
"__v":0
}
I want to store the value of the _id in a variable and use it later. I have been trying to for the last couple of days and nothing seems to work. Can anyone please help me. Thanks in advance.
The recommended way to use variables with cypress is with aliases. See docs here.
In your code, you can wrap() your _id and save it as an alias, then call your alias somewhere else in your code:
cy.wait('#newObject').then((response) => {
expect(response.status).to.eq(201)
expect(response.responseBody).to.have.property('_id')
cy.wrap(response.responseBody._id).as('newId')
})
// then later in your code use:
cy.get('#newId').then((newId) => {
cy.get(someInput).type(newId)
})
You could also type() your _id inside your wait():
cy.wait('#newObject').then((response) => {
expect(response.status).to.eq(201)
expect(response.responseBody).to.have.property('_id')
cy.get(someInput).type(response.responseBody._id)
})
Or you can use cypress global environmment object Cypress.env(). See docs here.
cy.wait('#newObject').then((response) => {
expect(response.status).to.eq(201)
expect(response.responseBody).to.have.property('_id')
Cypress.env('newId', response.responseBody._id)
})
// then later in your code use:
cy.get(someInput).type(Cypress.env('newId'))

How to unit test HTML being rendered from JSON?

I am having a little trouble working out how to UNIT test with JEST and vanilla JS as nothign is really coming up online.
I am calling an API endpoint and then rendering that data as HTML - A set of UL/ LI's and a sub menu if there is a submenu.
How would you go about breaking this function up to unit test it - I am not even really sure where to start
Here is the data
"items":[
{
"label":"Work",
"url":"#/work",
"items":[
]
},
{
"label":"About",
"url":"#/about",
"items":[
{
"label":"What we do",
"url":"#/about/what-we-do"
},
{
"label":"How we work",
"url":"#/about/how-we-work"
},
{
"label":"Leadership",
"url":"#/about/leadership"
}
]
},
{
"label":"foo",
"url":"#/foo",
"items":[
{
"label":"Client Services",
"url":"#/foo/client"
},
{
"label":"Creative",
"url":"#/foo/creative"
},
{
"label":"Motion & Media",
"url":"#/foo/motion"
}
]
}
]
}
Here is the function I am calling to create the DOM elements - Its slightly messy but it is essentially creating the anchor tags and Ul / Li's.
createNavigationMenu: function (data) {
return data.map((item) => {
const listElement = document.createElement('li');
const listElementAnchor = document.createElement('a');
const subMenuContainer = document.createElement('ul');
const chevron = document.createElement('span');
listElementAnchor.setAttribute("href", `${item.url}`);
listElementAnchor.setAttribute("class", 'navigation__primary-menu-anchor');
listElementAnchor.innerHTML = item.label;
listElement.setAttribute("class", "navigation__primary-menu-item");
listElement.appendChild(listElementAnchor);
this.navigationContainer.append(listElement);
subMenuContainer.setAttribute("class", "navigation__submenu");
item.items.length ? listElement.append(subMenuContainer) : null
chevron.setAttribute("class", "arrow");
item.items.length ? listElementAnchor.append(chevron) : null
return item.items.map(submenuItem => {
const subMenuContainerItem = document.createElement('li');
const subMenuContainerItemAnchor = document.createElement('a');
subMenuContainerItemAnchor.setAttribute("href", `/${submenuItem.url}`);
subMenuContainerItemAnchor.setAttribute("class", 'navigation__submenu-menu-anchor');
subMenuContainerItemAnchor.innerHTML = submenuItem.label;
subMenuContainerItem.setAttribute("class", "navigation__submenu-menu-item");
subMenuContainerItem.append(subMenuContainerItemAnchor)
listElement.append(subMenuContainer);
subMenuContainer.append(subMenuContainerItem)
})
})
}
I have tried this with JSDOM but it does not seem to work
const data = {
"items": [
{
"label": "Work",
"url": "#/work",
"items": [
]
}
]
}
const markup = `<ul id="navigation__primary-menu" class="navigation__primary-menu">
<li>
<h1 class="navigation__primary-logo">HUGE </h1> <span id="iconSpan" class="saved"> </span>
</li>
<li class="navigation__primary-list-item"></li>
<li class="navigation__primary-menu-item">Work</li>
</ul>`
describe('should map data correctly', () => {
test('markup entered', () => {
const { windpw } = new JSDOM(markup);
const nav = Controller.createNavigationMenu(data)
expect(dom.serialize()).toMatch(nav)
});
});
This answer is for guidance only. It will not 'just work.'
Your request is complex - tests are usually written in Node (which is server side) and you are talking about using document and the DOM (which is client side.)
I would suggest looking into https://github.com/jsdom/jsdom which allows you to emulate document in Node.
Then, from their docs, something like:
const dom = new JSDOM();
and update your generator like this:
createNavigationMenu: function (data, dom) { //pass in your dom object here
return data.map((item) => {
const listElement = dom.createElement('li');
.... //replace all documents with dom
Pass your special dom into your generator function when running tests, and pass the true document at other times.
Finally, in your test (using jest example):
describe('dom test', () => {
it('should render like this', () => {
expect(dom.serialize()).toMatchSnapshot()
}
}
As an aside, DOM manipulation is usually only necessary in this day and age if you're trying to be super clever. I strongly suggest (if you haven't considered it) trying to use a framework that is going to make your life much easier. I use React, which also comes with the added bonus of testing frameworks that allow you to test rendering. Other frameworks are available.

Error serving HTML files from an Azure function

I am trying to open, read and return an HTML files using Azure functions. I am developing locally and the logs says that the function executed successfully however on the browser I am getting 500 internal server error. Am I doing something wrong in here?
const fs = require('fs');
const path = require('path');
const mime = require('../node_modules/mime-types');
module.exports = function (context, req) {
const staticFilesFolder = 'www/build/';
const defaultPage = 'index.html';
getFile(context, req.query.file);
function getFile(context, file) {
const homeLocation = process.env["HOME"];
if(!file || file == null || file === undefined){
context.done(null,{status:200,body:"<h1>Define a file</h1>",headers:{
"Content-Type":" text/html; charset=utf-8"
}});
}
fs.readFile(path.resolve(path.join(homeLocation, staticFilesFolder, file)),
(err, htmlContent) => {
if (err) {
getFile(context, "404.html");
}
else {
const res = {
status: 200,
body: htmlContent,
headers:{
"Content-Type": mime.lookup(path.join(homeLocation, staticFilesFolder, file))
}
}
context.done(null,res);
}
})
}
};
Note
I am sure that 404.html exists and index.html exists. When I log the contents of htmlContent it is giving the correct output.
functions.json
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"methods":["get"],
"route":"home",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Response on Chrome
If I removed "Content-Length" header the status code changes to 406.
Update 1 The code seems to be running normally on Azure Portal but it is not working when running it locally.
It looks like you are combining two methods of returning data from an http triggered function(context.res and context.done()): https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node#accessing-the-request-and-response
Since you are using context.res, try removing context.done();
You are making an incorrect use of context.res, you shouldn't be overwriting it but instead leveraging the methods provided by the Response class provided in the Azure NodeJS worker. If you are using using VSCode you'll get intellisense for these methods. Otherwise see: https://github.com/Azure/azure-functions-nodejs-worker/blob/dev/src/http/Response.ts
Your code should look something like this instead.
context.res.setHeader('content-type', 'text/html; charset=utf-8')
context.res.raw(htmlContent)
Using context.res.raw or context.res.send will already perform the context.done call for you.
Make sure you use content-type=text/html; charset-utf8 instead of content-type=text/html or you'll trigger an issue with the returned content-type. Instead of returning content-type=text/html you end up getting content-type=text/plain which will fail to render your html.
Addressed on: https://github.com/Azure/azure-webjobs-sdk-script/issues/2053

webdriverio browser.execute returns null

I have a meteor app and want to retrieve some data inside a unit test from the client via a headless browser from webdriver.io.
The data I want is from this function:
Session.get() -> http://meteortips.com/first-meteor-tutorial/sessions/
The headless browser I use is from below URL:
http://webdriver.io/
My test looks like this:
describe('[Check Boards]', () => {
it('should exist', () => {
const board = browser.execute('Session.get(\'currentBoard\')');
...
}
}
When i run this command Session.get('currentBoard') inside a real browser console, I get the board as expected.
But when I run it from the code like described above inside a mocha test, I get this result:
{
"state": "success",
"sessionId": "12345",
"hCode": 12345,
"value": null,
"class": "org.openqa.selenium.remote.Response",
"_status": 0
}
The value is null, but there should be the board.
browser.execute expects a function to run in the browser. You're passing in a string, so it probably doesn't know what to do. Here's an updated code snippet that should work:
describe('[Check Boards]', () => {
it('should exist', () => {
const board = browser.execute(function () {
return Session.get('currentBoard');
});
...
}
}
If you're looking for more details, I have an 8 minute video on browser.execute in my WebdriverIO course (#23 in the list).

Categories