Asynchronous functions in beforeEach not finishing before test block - javascript

I am trying to write test code for a project management software using Jest. The software is written with Javascript, and uses MongoDB for the database. The project uses an object model hirecarchy that goes:
User => Project => Backlog => Story => Task
I use an external script to populate the test database before running the tests in the test file using a beforeEach block. So far, the populate script makes a couple of users. Then assigns a couple of projects to a chosen user. Then assigns a couple of backlogs to a chosen project. Then assigns a couple of stories to a chosen backlog.
This method has worked for the tests on the user to the backlog model. Now I am writing the test for the story model and I am running into a problem where by the time the code in the test block is executing, the test database is not completely populated.
I have used breakpoints and MongoDBCompass to see what is in the database by the time the code is in the test block, and it appears that database is populated to varying extents during it run. It seems as though the code populating the database is lagging jests execution queue. Is there anyway I can ensure the database population is done before I enter the test block?
Before each block in the story model test file
beforeEach( async () => {
await User.deleteMany();
await Project.deleteMany();
await Backlog.deleteMany();
await Story.deleteMany();
populate.makeUsers();
populate.assignProjects(userXId, userX.userName);
populate.assignBacklogs(populate.projects[1]);
await populate.assignStories();
await new User(userX).save();
});
The function populating the database with Stories
this.assignStories = async function () {
const pBacklog = await Backlog.findOne({project: this.projects[1]._id, name: "Product Backlog"})
const temp = this
if (pBacklog != undefined) {
let pBacklogID = pBacklog._id
this.stories = createStories(14, this.projects[1]._id, pBacklogID, this.backlogs[0]._id);
this.stories.forEach(async (story, index) =>{
let newStory = new Story(story);
this.stories[index] = newStory;
await newStory.save();
})
} else {
setTimeout(async function () {await temp.assignStories()}, 200)
}
}
I have excluded the functions for populating the other models to keep this short but I can add it if it will help with the problem.
Thank you.

Thank you #Bergi. The problem was with using forEach with asyn/await functions. I refactored my code using Do not use forEach with an asynchronous callback.

Related

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

How to run test suite in parallel but not the single tests

I'm using Playwright.dev to automate our UI tests. Currently I face the following issue:
In a single spec.ts file I have different test suites. Those test suites should run in parallel but not each test. I can not split those test suites into separate files because they are created dynamically. Why I want to run the tests of each test suite serial is, to reuse the existing page. Because it's much faster to just reuse the page without doing a complete refresh of the page the whole time. I'll try to explain my problem with some pseudo-code:
catalogs.forEach((objectIdsOfCatalog, catalogId) => {
// each suite could run in parallel because they do not
// depend on each other
test.describe('Test catalog "' + catalogId + '"', () => {
let newVersion: PageObject;
let actualVersion: PageObject;
test.beforeAll(async ({browser}) => {
console.log('New page for', catalogId);
const {actualUrl, newUrl} = getConfig();
const context = await browser.newContext();
actualVersion = new PageObject(await context.newPage(), actualUrl);
newVersion = new PageObject(await context.newPage(), newUrl);
});
test.afterAll(async () => {
console.log('Close page for', catalogId);
actualVersion.close();
newVersion.close();
actualVersion = null;
newVersion = null;
});
// those tests should ran serial because it's faster
// if we just navigate on the existing page due to the
// client side caching of the web app
for (const objectId of objectIdsOfCatalog) {
test('Testing "' + objectId + '"', async () => {
});
}
});
});
Is there some way to achieve the following behavior in Playwright or do I have to rethink my approach?
I don't know if multiple test.describe.serial blocks could be nested in a test.describe.parallel (and if that works), but maybe that's worth a try.
Another option could be to not generate real tests, but just to generate steps (test.step) inside tests inside a test.describe.parallel block.
And where do the catalogs come from? Maybe instead of generating describe blocks, you could generate projects in the playwright.config.ts? Projects run in parallel by default I think. But don't know if that approach would work if the data is coming from some async source.

Is there a way to run particular Protractor test depending on the result of the other test?

This kind of question has been asked before but most of this question has pretty complicated background.
The scenario is simple. Let's say we are testing our favorite TODO app.
Test cases are next:
TC00 - 'User should be able to add a TODO item to the TODO list'
TC01 - 'User should be able to rename TODO item'
TC02 - 'User should be able to remove TODO item'
I don't want to run the TC01 and TC02 if TC00 fails (the TODO item is not added so I have nothing to remove or rename)
So I've been researching on this question for the past 3 days and the most common answers fro this question are:
• Your tests should not depend on each other
• Protractor/Jasmine does not have feature to dynamically turn on/off tests ('it' blocks)
There reason why I'm asking this question here is because it looks like a very widespread case and still no clear suggestion to handle this (I mean I could not find any)
My javascript skills are poor but I understand that I need to play around, let's say' passing 'done' or adding the if with the test inside...
it('should add a todo' ()=> {
todoInput.sendKeys('test')
addButton.click();
let item = element(by.cssContainingText('.list-item','test')
expect(item.isPresent()).toBe(true)
}
In my case there are like 15 tests ('it' blocks) after adding the item to the list. And I want to skip SOME OF THE tests if the 'parent' test failed.
PLEASE NOTE:
There is a solution out there which allows to skip ALL remaining test if one fails. This does not suit my needs
Man, I spent good couple of weeks researching this, and yes there was NO clear answers, until I realized how protractor works in details. If you understand this too you'll figure out the best option for you.
SOLUTION IS BELOW AFTER SHORT THEORY
1) If you try to pass async function to describe you see it'll fail, because it only accepts synchronous function
What it means for you, is that whatever condition you want to pass to it block, it can't be Promise based (Promise == resolves somewhen, but not immediately). What you're trying to do essentially IS a Promise (open page, do something and wait to see if the condition satisfies your criteria)
if (conditionIsTrue) { // can't be Promise
it('name', () => {
})
}
Thats first thing to consider...
2) When you run protractor, it picks up spec files specified in config and builds the queue of describe/it AND beforeAll/afterAll blocks. IMPORTANT DETAIL HERE IS THAT IT HAPPENS BEFORE THE BROWSER EVEN STARTED.
Look at this example
let conditionIsTrue; // undefined
it('name', () => {
conditionIsTrue = true;
})
if (conditionIsTrue) { // still undefined
it('name', () => {
})
}
By the time Protractor reaches if() statement, the value of conditionIsTrue is still undefined. And it maybe overwritten inside of it block, when browser starts, later on, but not when it builds the queue. So it skips it.
In other words, protractor knows which describe blocks it'll run before it even opens the browser, and this queue can NOT be modified during execution
POSSIBLE SOLUTION
1.1 Define a global variable outside of describe
let conditionIsTrue; // undefined
describe("describe", () => {
it('name1', async () => {
conditionIsTrue = await element.isPresent(); // NOW IT'S TRUE if element is present
})
it('name2', async () => {
if (conditionIsTrue) {
//do whatever you want if the element is present
} else {
console.log("Skipping 'name2' test")
}
})
})
So you won't skip the it block itself, however you can skip anything inside of it
1.2 The same approach can be used for skipping it blocks across different specs, using environment variable. Example:
spec_1.js
describe(`Suite: 1`, () => {
it("element is present", async () => {
if (await element.isPresent()) {
process.env.FLAG = true
} else {
process.env.FLAG = false
}
});
});
spec_2.js
describe(`Suite: 2`, () => {
it("element is present", async () => {
if (process.env.FLAG) {
// do element specific actions
}
});
});
Another possibility I found out, but never had a chance to check is to use Grunt task runner, which may help you implement the following scenario
Run protractor to execute one spec
Check a desired condition
Export this condition to environment variable
Exit protractor
In your Grunt task implement a conditional logic for executing the rests of conditional specs, by starting protractor again
But honestly, I don't see why you'd want to go this time consuming route, which requires a lot of code... But just as an FYI
There is one way provided by Protractor which might achieve what you want to achieve.
In protractor config file you can have onPrepare function. It is actually a callback function called once protractor is ready and available, and before the specs are executed. If multiple capabilities are being run, this will run once per capability.
Now as i understand you need to do a test or we can say execute a parent function and then based on its output you want to run some tests and do not want to run other tests.
onPrepare function in protractor config file will look like this :
onPrepare: async () => {
await browser.manage().window().maximize();
await browser.driver.get('url')
// continue your parent test steps for adding an item and at the last of function you can assign a global variable say global.itemAdded = true/false based on the result of above test steps. Note that you need to use 'global.' here to make it a global variable which will then be available in all specs
}
Now in you specs file you can run tests (it()) based on global.itemAdded variable value
if(global.itemAdded === true) {
it('This test should be running' () => {
})
}
if(global.itemAdded === false) {
it('This test should not be running' () => {
})
}

Create dynamic tests for TestCafe asynchronously

I am creating tests in TestCafe. The goal is to have the tests written in Gherkin. I looked at some GitHub repositories which integrate Cucumber and TestCafe but I am trying a different angle.
I would like to use the Gherkin parser and skip Cucumber. Instead I will create my own implementation to run the teststeps. But currently I am stuck trying to get TestCafe to run the tests.
If I am correct the issue is that TestCafe is running my test file, and then sees no fixtures or tests anywhere. Which is correct because the Gherkin parser is using the stream API (it uses a seperate Go process to parse the feature files) to deliver the data, which means that in my current code the Promise is still pending when TestCafe quits. Or if I remove that the end callback hasn't happened yet.
Is my analysis correct? If yes how can I get all the data from the stream and create my tests such that TestCafe will run it?
gherkin_executor.js
var Gherkin = require('gherkin');
console.log('start')
const getParsedGherkin = new Promise((resolve, reject) => {
let stream = Gherkin.fromPaths(['file.feature'])
let data = []
stream.on('data', (chunk) => {
if(chunk.hasOwnProperty('source')){
data.push({source: chunk.source, name: null, pickles: []})
}
else if (chunk.hasOwnProperty('gherkinDocument')){
data[data.length-1].name = chunk.gherkinDocument.feature.name
}
else {
data[data.length-1].pickles.push(chunk.pickle)
}
})
stream.on('end', () => {
resolve(data)
})
})
let data = getParsedGherkin.then((data) => {return data})
console.log(data)
function createTests(data){
for(let feature of data){
fixture(feature.name)
for(let testcase of feature.pickles){
test(testcase.name, async t => {
console.log('test')
})
}
}
}
file.feature
Feature: A test feature
Scenario: A test case
Given some data
When doing some action
Then there is some result
Nice initiative!
To go further in your approach, the method createTests must generate the TestCafe code in at least one JavaScript or TypeScript file. Then you must start the TestCafe runner from these files.
So now, to go further in your approach, you must write a TestCafe source code generator.
Maybe the hdorgeval/testcafe-starter repo on GitHub could be an alternative until Cucumber is officially supported by the TestCafe Team.

Stubbing variables in a constructor?

I'm trying to figure out how to properly stub this scenario, but i'm a little stuck.
The scenario is, i've got a db.js file that has a list of couchdb databases in it (each database contains tweet entries for a particular year).
Each year a new database is created and added to this list to hold the new entries for that year (so the list of databases isn't constant, it changes each year).
So my db.js file looks like this:
var nano = require('nano')(`http://${host}`);
var databaseList = {
db1: nano.use('db2012'),
db2: nano.use('db2013'),
db4: nano.use('db2014'),
db5: nano.use('db2015'),
db6: nano.use('db2016')
};
module.exports.connection = nano;
module.exports.databaseList = databaseList;
And event.js (a simple model file), before methods are added looks like this:
var lastInObject = require('../../helpers/last_in_object');
var db = require('../../db');
var EventModel = function EventModel() {
this.connection = db.connection;
this.databaseList = db.databaseList;
this.defaultDatabase = lastInObject(db.databaseList);
};
EventModel.prototype.findAll =
function findAll(db, callback) {/* ... */}
My question is, how do i stub the databaseList, so i can safely test each of the model methods without having any brittleness from the growing databaseList object?
Ideally i'd like to be able hijack the contents of the databaseList in my tests, to mock different scenarios, but i'm unsure how to tackle it.
Here's an example test, to ensure the defaultDatabase property is always pointing to the last known event, but obviously i don't want to have to update this test every year, when databaseList changes, as that makes the tests very brittle.
it('should set the default database to the last known event', () => {
var Event = require('../event');
var newEventModel = new Event();
expect(newEventModel.defaultDatabase.config.db)
.to.equal('db2014');
});
Suggestions welcome! If i've gone about this wrong, let me know what i've done and how i can approach it!
Also, this is just a scenario, i do have tests for lastInObject, i'm more interested in how to mock the concerning data.
In my opinion you need to stub the whole "db" module. That way you won't have any real db connection and you can easily control the environment of your tests. You can achieve this by using the mockery module.
That way you can stub the object that the require('../../db') returns. This will allow you to set whatever value you like in the properties of that object.
Building upon Scotty's comment in the accepted answer a bit... here's some code I've got which seems to do the job. No guarantees that this is a good implementation, but it does successfully stub out the insert and get methods. Hopefully it helps someone. :)
// /src/common/database.js
const database = require('nano')('http://127.0.0.1/database');
module.exports = {
async write(document, documentId) {
return await new Promise(resolve => database.insert(document, documentId, resolve));
},
async read(documentId){
return await new Promise(resolve => database.get(documentId, resolve));
}
};
// /test/setup.js
const proxyquire = require('proxyquire');
proxyquire('../src/common/database.js', {
nano() {
return {
insert(document, documentId, callback){ callback(); },
get(documentId, callback){ callback(); }
};
}
});

Categories

Resources