Can we run a dynamical loop test in mocha when we obtain the dynamic array from one of the test cases something like below?
describe('test', () {
var array = [];
it('test 1', function(done) {
// get the array
// assume JSON = [ {number : 1, value : 1}, {number : 2, value : 2}]
}
// run loop test base on the array from test 1
array.forEach() {
it(`test {array.number}`, function(done) {
// sample
expect(array.number).to.be.equal(array.value)
}
}
it('test 3', function(done) {
// another test
}
}
So far, it seems to work fine if I ran in a separate js file instead of just one js file by passing the result of the JSON array (callback) to another test js file. What if I want it in one js file?
Reference: https://mochajs.org/#dynamically-generating-tests
Related
Problem:
Trying to call tests within an array of items returned from a Cypress custom command.
Approaches attempted using npm package mocha-each and another test using the forEach function.
Custom Command:
I created a custom Cypress command that returns an array of AppParamsType:
/// <reference types="Cypress" />
import { AppParamsType } from 'support';
declare global {
namespace Cypress {
interface Chainable {
cmdGetAppsParams: () => Chainable<AppParamsType[]>;
}
}
}
export function cmdGetAppsParams() {
const paramsApps: AppParamsType[] = [];
cy.cmdAppKeys()
.then(($appKeys: string[]) => {
cy.wrap($appKeys).each(($appKey: string) => {
cy.cmdProviderAppParams($appKey).then((paramApp: AppParamsType) => {
paramsApps.push(paramApp);
});
});
})
.then(() => {
return cy.wrap(paramsApps);
});
}
Cypress.Commands.add('cmdGetAppsParams', cmdGetAppsParams);
Test using Custom Command:
The following Cypress test calls the custom command cmdGetAppsParams() to return an array of items.
The array is being iterated with one test using npm package mocha-each and another test using Array forEach. Neither approach calls the tests within the loops.
import * as forEach from 'mocha-each';
let apps: AppParamsType[];
describe('DESCRIBE Apps Add Apps Spec', () => {
before('BEFORE', () => {
cy.cmdGetAppsParams().then(($apps: AppParamsType[]) => {
expect($apps).to.be.an('array').not.empty;
apps = $apps;
});
});
it('TEST Apps Params Array', () => {
cy.task('log', { line: 'A', data: apps });
expect(apps).to.be.an('array').not.empty;
});
it('TEST each item mocha forEach', () => {
cy.task('log', { line: 'B', data: apps });
forEach(apps).it('item', (item: AppParamsType) => {
cy.task('log', { line: 'B.1', data: item });
expect(item).to.be.an('object').not.null;
});
});
it('TEST each item array forEach', () => {
cy.task('log', { line:'C', data: apps });
expect(apps).to.be.an('array').not.empty;
apps.forEach((item: AppParamsType) => {
it('TEST App Param', () => {
cy.task('log', { line: 'C.1', data: item });
expect(item).to.be.an('object').not.null;
});
});
});
The results I am seeing is that the outer tests, indicated by labels 'A', 'B' and 'C', are getting called. But, not the inner tests, which would be indicated by labels 'B.1' and 'C.1':
{
"line": "A",
"data": [
***
]
}
{
"line": "B",
"data": [
***
]
}
{
"line": "C",
"data": [
***
]
}
Nesting an it() inside an it() looks novel. I'm surprised you are not getting an error from it.
The basic problem when generating tests dynamically is that the Cypress runner needs to know exactly how many tests will be generated before it starts running them. But any Cypress commands (including custom commands) will not run until the entire test script has finished running (excluding callback code), so you can't get the apps list from a custom command.
The best way to proceed is to convert cy.cmdAppKeys(), cy.cmdGetAppsParams(), and cy.cmdProviderAppParams() from custom commands to a plain javascript function, and then run that function at the top of the script, e.g
const apps = getMyApps(); // synchronous JS function,
// will run as soon as the test starts
apps.forEach((item: AppParamsType) => {
const titleForTest = `Test App Param ${item.name}`; // Construct an informative title
it(titleForTest, () => {
...
})
})
If you can provide details of the custom commands cy.cmdAppKeys() and cy.cmdProviderAppParams(), may be able to help convert to the synchronous function.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I'm new to javascript and especially node and would appreciate some help.
I have the following code which looks up delivery status for various packages. Everything works fine except the final step, which is writing the results to a CSV file using the csv-writer package from npm.
I process each line of the source CSV and populate an array with the results. The array is declared at the top of the file as a const (final_result) outside of any function. In my understanding, this means it can be accessed by all the functions in the code. Is this right?
The problem I'm having is that when I get to the final step (writing the results to a new CSV file), the final_result length is 0 and contains no data !
When I console-log in the first and second functions, there is data showing.I can also see the array being populated as each row is processed
What am I missing or is there a better way (best practice? ) to achieve this. Thanks.
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const yt = require('yodel-tracking');
const csv = require('csv-parser');
const fs = require('fs');
const final_result = []; // This is showing as empty at the end ?!
const csvWriter = createCsvWriter({
path: 'out.csv',
header: [
{ id: 'Location', title: 'Location' },
{ id: 'Ref', title: 'Ref' },
{ id: 'Scan', title: 'Scan' },
{ id: 'Status', title: 'Status' }
]
});
//Functions in order of how they are called
//1 of 3
function run() {
fs.createReadStream('yodeldata.csv')
.on('error', () => console.log('error'))
.pipe(csv())
.on('data', row => {
output(row); //calling function 2 of 3
});
}
//2 of 3
function output(row) {
yt(row['REF'], 'GB', function(err, result) {
let data = result.parcel_tracking.parcel_status[0];
if (!err) {
let obj = {
Ref: `${row['REF']}`,
Location: `${data.location}`,
Scan: `${data.scan_date} ${data.scan_time}`,
Status: `${data.status_description}`
};
final_result.push(obj); //Pushing each result to the array here
console.log(final_result.length); // I can see the length increasing sequentially here
}
});
}
//3 of 3
function resultToCsv() {
console.log('Number of records to be written to csv: ' + final_result.length); // Why is this Zero ?? Expected an array of objects here
csvWriter
.writeRecords(final_result)
.then(() => console.log('The CSV file was written successfully'));
}
//Function calls
run();
resultToCsv();
This issue here is that when you do this:
run();
resultToCsv();
Those two functions are being called immediately. Because run() is using asynchronous methods, it won't have finished reading the stream before resultToCsv() is called. So you want to wait for the stream to be done being read before calling that.
function run() {
fs.createReadStream('yodeldata.csv')
.on('error', () => console.log('error'))
.pipe(csv())
.on('data', row => {
output(row); //calling function 2 of 3
})
.on('close', resultToCsv);
}
So I have these 2 cases in my tests. First one works fine, in the second one I try to extract the beforeEach declaration outside and it fails but I don't understand why. This is a simple case, basically I try to define an array and make a loop on that in order to run the tests multimple time with different beforeEach params declaration.
CASE 1
var params;
describe('When initializing', function () {
beforeEach(function () {
params = {
name: 'test 1'
};
});
it('should ..', function () {
params.name = 'test 2';
expect(...); => success
});
it('should ..', function () {
expect(...); => success because it expects params.name to be 'test 1' and it is 'test 1'
});
});
CASE 2
var params;
var test = {
name: 'test 1'
};
describe('When initializing', function () {
beforeEach(function () {
params = test;
});
it('should ..', function () {
params.name = 'test 2';
expect(...); => success
});
it('should ..', function () {
expect(...); => fails because it expects params.name to be 'test 1' and it is 'test 2'
});
});
In the second test if I console.log(test.name) inside the describe I will get test 2, somehow it got overriden even though the previous it did just params.name = 'test 2'; and not test.name = 'test 2';
The difference is that in case 1 you're creating a new object every time beforeEach is called, while in case 2 you're not.
Combined with that is the fact that your first test mutates the object. If all the tests are referring to the same object (ie, case 2) then that mutation will affect any code that runs after the first test. If instead the object is overwritten before each test (case 1), then the mutation won't affect other tests.
There are a few options for how to address this. One is to just to keep case 1; by resetting to a known state each time, you can have a clean state for all the tests to work off of. Another option is to not mutate the object. Perhaps the tests could copy the object and then modify that copy.
I have the following scenario:
In my test, I declare a variable
const query = {
where: { publishedCount: { inq: [2, 3] } },
};
and when I check if the variable is an object using .constructor === Object I get true
console.log(query.constructor === Object); // true
In the next line I pass the same variable to the function and inside that function the comparison returns false
app.models.Group.find(query);
function find(query) {
console.log(query) // prints: where: { publishedCount: { inq: [2, 3] } },
console.log(query.constructor) // prints: function Object() {}
console.log(query.constructor === Object) // FALSE
}
My code runs on Node v8.10.0 and Jest runner v23.4.2. In both cases, printing query returns correct object and printing query.constructor returns function Object() {}
I made a simple check using JSBIn and there the comparisons return true two times
https://jsbin.com/wotuperode/edit?js,console
What is this happening?
PS. I can't change how I check for Object type because one of the libs from node_modules checks in this way
UPDATE 1
The function that I call is the find function from loopback library. This find function is available in every data model in your app.
https://github.com/strongloop/loopback-datasource-juggler/blob/master/lib/dao.js#L1897
The comparison used by lib is here: https://github.com/strongloop/loopback-connector-postgresql/blob/master/lib/postgresql.js#L554
(the lib adds postgres connector to loopback)
My full code
describe("Tests", () => {
it("update array of elements", async () => {
// creating objects etc
...
const query = {
where: { publishedCount: { inq: [2, 3] } },
};
await app.models.Group.find(query); // my code has await but even without await the comparison still returns false
...
// expects etc
});
});
UPDATE 2
It's only happening in my tests when I run code using Jest runner.
i want to implement jasmine test with the next code:
This is my filter-service.js
function FilterService() {
}
FilterService.prototype.filter = function (companies, filter) {
return companies;
};
And my filter-service-spec.js is
describe("Filter service filter(...) tests", function() {
var filterService = new FilterService();
var allTestCompanies= [
{ id: 1, name: "company1 Test", admin: "Test Admin" },
{ id: 2, name: "company2", admin: "Test Admin", country: 'London' },
{ id: 3, name: "company3", admin: "Mery Phill" }
];
it('returns original collection if called with undefined filter', function() {
var input = [1, 2, 3];
var result = filterService.filter(input, undefined);
expect(result).toEqual(input);
});
it('returns original collection if called with empty filter', function () {
var input = [2, 6, 7];
var result = filterService.filter(input, '');
expect(result).toEqual(input);
});
it('only includes matching companies once', function() {
var result = filterService.filter(allTestCompanies, 'Test');
expect(result.length).toEqual(2);
});
it('matches exact text on company name', function() {
var result = filterService.filter(allTestCompanies, "company1 Test");
expect(result[0]).toEqual(allTestCompanies[0]);
});
it('matches exact text contained in company name', function () {
var result = filterService.filter(allTestCompanies, "Test");
expect(result[0]).toEqual(allTestCompanies[0]);
});
it('matches case invarient text contained in company name', function () {
var result = filterService.filter(allTestCompanies, "test");
expect(result[0]).toEqual(allTestCompanies[0]);
});
it('matches exact text of admin', function() {
var result = filterService.filter(allTestCompanies, 'Mery Phill');
expect(result[0]).toEqual(allTestCompanies[2]);
});
it('matches exact text in admin', function () {
var result = filterService.filter(allTestCompanies, 'Phil');
expect(result[0]).toEqual(allTestCompanies[2]);
});
it('matches case invarient text in admin', function () {
var result = filterService.filter(allTestCompanies, 'PHIl');
expect(result[0]).toEqual(allTestCompanies[2]);
});
});
How i can implement a function in filter-service.js for pass the javascript tests. For now only pass the 2 first.
Sure, this is actually very easy to do, but I will provide just with approach you should take. So lets begin ...
I hope you have an environment where you able to run your tests (Jasmine installed and you able to run your tests against "FilterService" object and "filter" method in particular). You said two first methods are succeeded.
Each Jasmine "it" function has description, for example "matches exact text on company name" (test #4). This is most important part you will be dealing with. We will talk about this test. Ask yourself what this description tells you? This means if you pass into "filter" method exact name of the company, the function should go through array of given companies objects and try to find match for exact company name.
Next you would need to look at implementation of Jasmine "expect" function. You will notice that returned result from the "filter" function call must be equal to the first element of the given array of companies because the name of the company in the filter parameter match to this object's "name".
After you implement this part and test succeeded, you would go to next test and add/change existing implementation to accomplish next "it" description.
This is called TDD (test driven development). Please read a bit on it before you start working.
Finally lets try to implement this test #4. Please note the code provided may not work, but as we agreed you will make it work, I just show the approach ...
function FilterService() {
}
FilterService.prototype.filter = function (companies, filter) {
// this is our result variable which we will return at the end
var result;
// for test #1 the "filter" parameter is undefined and the test description says "returns original collection if called with undefined filter" (#2 will look similar to test #1, add this by yourself)
if (typeof filter === "undefined") {
result = companies;
}
// jump to test #4 we are talking about. "it" says "matches exact text on company name"
companies.forEach( function (companyObj) {
if (companyObj.name === filter) {
result = [companyObj];
return false;
}
});
// continue to modify the function to meet all criteria of every "it" description
// we return our result
return result;
};