I am just starting out with Jest, and I am trying to test this code that will change the textContent of an element after 1000ms:
const subtext = document.querySelector('.subtext');
function delayChangeText() {
setTimeout(() => {
subtext.textContent = "Dev";
}, 1000);
}
subtext.addEventListener('load', delayChangeText);
This is what Jest returns:
FAIL js/app.test.js
● Test suite failed to run
Cannot find module './delayChangeText' from 'js/app.test.js'
> 1 | const delayChangeText = require('./delayChangeText');
| ^
2 |
3 | test('Change the text after 1000 seconds', () => {
4 | expect(delayChangeText().toBe(subtext.textContent = "Dev"));
at Resolver.resolveModule (node_modules/jest-resolve/build/resolver.js:311:11)
at Object.<anonymous> (js/app.test.js:1:1)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.846 s
I am still pretty new to testing, I'm confident I made a pretty simple goof. Any help is much appreciated. Best regards.
The zeroth rule of testing is:
Code must be written such that it is testable
Not all code can be tested. Sometimes you have to change how your real code is written so that a testing framework can get its hands on the code.
I can see one or two critical problems.
First: I assume you didn't include the full content of your application, but it does not look like your app code exports the delayChangeText function, which means that other modules (such as your test suite) can't import it.
You may need to do something like module.exports = delayChangeText, or export default delayChangeText in your app code.
Second: your function is not a pure function. That is, it depends on stuff that's not passed to it explicitly, namely it expects that subtext is defined within its execution context.
It's not strictly required that all your functions be pure functions, and indeed sometimes it's not possible. But pure functions are usually much easier to test (as well as being easier to design and implement). Here's a pure version of your function:
function delayChangeText(element) {
setTimeout(() => {
element.textContent = "Dev";
}, 1000);
}
You don't have to convert this to a pure function, but your code will break in the test unless your test suite takes steps to ensure that subtext.textContent doesn't throw -- if subtext is undefined, it will throw.
This is important for another reason: if this module's default export is the delayChangeText function, then it's probably not appropriate for the preceding subtext assignment to even be in the file. Which means that fixing the first problem ("it's not being exported") will naturally result in converting the function to a pure function. If you really want to avoid that, you can: you'll probably have to change how the function is imported in the test suite, to this:
const { delayChangeText } = require('./delayChangeText');
Finally (and you didn't ask about this -- yet): you probably don't want this test to have to actually wait 1000 ms to test this function. Jest has some tools for manipulating the global timer functions, which will allow you to validate the function without having to wait.
Related
I want to run certain tests depending on certain conditions. If the condition is met then run, otherwise skip the test. I want to do that before being in the test (or maybe jest provide a function to cancel a test inside it? So that it doesn't throw an error)
You can see here what I have :
The setup: I'm testing different crypto-wallet for a DEX, so I have a battery of tests that runs for all those wallets. By design, some features are on some wallets and others don't have them. Here I'm trying to sign a transaction running on an EVM chain different from Ethereum (AVAX here). This will not work on all wallets (normal) like ethwallet, but will for others. I want to test only those that I know support this feature.
My problem: wallet is initialized in beforeAll, and I don't have access to this object outside test() (the expected log is k2, k1 is out of scope and return gibberish). How can I access it then and know if I run the test or skip it? You can see that I tried to implement testif() but unfortunately not working because I have not the good value outside of test().
According with this Github issue you can do something like:
const runTest = wallet ? it : it.skip
runTest( "testName", async () => {
// ...
}
And the code will execute it or it.skip.
Introduction
Im using https://github.com/cucumber/cucumber-js in typescript. Sample of code implementation looks like average cucumber implementation:
import {
Given,
Then,
When
} from 'cucumber'
Given(`Page is up and running`, function(this: World) {
someFunction()
})
Goal
What I would like to achieve is to be able to somehow decorate Given, so that:
some action can be invoked before code inside Given. Lets say, for simple example, I would like to print first Given argument (so Page is up and running).
there would be no need to modify existing step implementation.
Each step got two alias function definitions in index.d.ts, e.g.:
export function Given(pattern: RegExp | string, code: StepDefinitionCode): void;
export function Given(pattern: RegExp | string, options: StepDefinitionOptions, code: StepDefinitionCode): void;
In the original version of CucumberJS, there were more hooks available including beforeStep. However, there is a way to achieve this functionality in versions of the framework such as 6.0.5 using setDefinitionFunctionWrapper. Example:
setDefinitionFunctionWrapper(fn => async function step(...args) {
//
// do something before the step definition runs.
//
const result = await fn.apply(this, args);
//
// do something after the step definition runs.
//
return result;
});
Online Example: https://testjam.io/?p=O2wodDVOveAMdA9HKMKy
Known limitations and caveats:
This function will wrap every step definition AND hook.
This particular implementation assumes that the step definition or hook is not using a callback.
I'm not sure how to make callbacks and promises work here at the same time, it may be possible but you'll have to manage that yourself by possibly inspecting the type of the last parameter and then handling it from there.
Update: The beforeStep and afterStep hooks are available in CucumberJS 7.x and later
I'm looking for a way to set a total time limit test with Jest, i.e. something like expect(somefunction()).toTake(1000). I'm aware that the second parameter for the test is a timeout for async functions, but I'm specifically looking to test the performance of the entire function (both async and non async parts) and have the test pass / fail in relation to the time it took to run the function.
Here is a comment which may help you in a related issue in jest repository; https://github.com/facebook/jest/issues/2694#issuecomment-411499373
Also, here is the code piece from that comment.
it('Should create 1000 objects pretty fast', async () => {
var start = new Date()
// Do expensive thing 1000 times
var after_save_all = new Date()
expect(after_save_all.getTime() - start.getTime()).toBeLessThanOrEqual(3000);
})
I have a protractor-cucumber framework whose step definitions are somewhat structured as per this: https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/step_definitions.md
I use a return and chain the promises together. Recently, I came across a different syntax called the async function. But, when I try to convert my step definitions to async, all the help files in the framework where I use say module.exports and require() display the following warning:
[ts] File is a CommonJS module; it may be converted to an ES6 module.
When I run test cases since I can't access these helper files due to the error my tests cases fail. Like, my page object files, I am not able to access them from my tests. I think they don't get exported like they used to.
Could someone please advice me as to how I can change my test cases to async syntax without breaking them? How do I resolve the above issue without disrupting my tests in a major way.
Adding code
Here is a step from my step definition before the change
let { Given, Then, When } = require('cucumber');
Given(/^I am on the "([^"]*)" page$/, function (home) {
home = this.url.FDI_HOME;
return browser.get(home);
});
Here is a step definition, after I change it to an async function
let { Given, Then, When } = require('cucumber');
Given(/^I am on the "([^"]*)" page$/, async function (home) {
home = this.url.HOME
await browser.get(home);
});
And I will change my other steps in similar fashion. Problem arises when I try to run the above step it fails saying that it is not able to access this.url.HOME. I have another file to supply URLs called the urls.js looks something like this
let targetStore = browser.params.store || 'bestbuy';
let FDI_HOST = browser.params.fdi;
module.exports = {
HOME Page: 'https://homepage.com',
Shop_Page: 'https://shop.com',
storeLink: `http://www.${targetStore}.com`,
};
I see three dots under the word "module.exports" in VS code and when I hover over it, it displays an error saying: [ts] File is a CommonJS module; it may be converted to an ES6 module.
I have tried to find a resolution to this but not been able to successfully make it. if I use the syntax as "async()=>{}" the test cases fails but when I use "async function(){}" then a few of the steps pass but not the other.
These are suggestions/hints. They visually indicate that vscode can perform an action to possibly refactor/improve your code, but they are not treated as errors.
You can disable them by adding "javascript.suggestionActions.enabled": false to your user/workspace settings.
Source: https://github.com/Microsoft/vscode/issues/47299
In each spec I have beforeEach and afterEach statements. Is it possible to add it somehow globally to avoid code duplication between specs ?
Purpose of beforeEach() and afterEach() functions are to add a block of repetitive code that you would need to execute every time you start or complete executing each spec(it). There are other ways to add generalised code to avoid code repetition, here are few -
If you have a piece of code that you would require to run only once before starting a test suite(describe), then you can use beforeAll() and afterAll() functions that jasmine provides.
If you want to run a piece of code that you want to run only once when the execution starts before starting all the test scripts, then add it in your onPrepare() and onComplete() function.
If you want to add a piece of code that should run even before protractor has started instantiating itself or after it has shut itself down, then use beforeLaunch and afterLaunch.
So it all depends on the scenario that you want to use them in. Hope it helps.
My team has the same desire, to run bits of boilerplate code at the start of every test file. From the discussion here, it doesn't sound like there are hooks to globally add to the beforeEach(), afterEach(), etc.
However, we do use the onPrepare() function to abbreviate the amount of before/after boilerplate code that gets repeated in each spec file. Below is a beforeAll() example, but the pattern could be used for beforeEach()/afterEach(). In this case, we're setting up test users in the database with a DataSeeder class, which we do in the outer-most describe() block in every spec file. (I'm also leaving in my catchProtractorErrorInLocation pattern, because it's super useful for us.)
In protractor.conf.ts add boilerplate code to browser.params object.
onPrepare: function () {
...
const browser = require('protractor').browser;
// Define the ConsoleHelper & DataSeeder instances, which will be used by all tests.
const DataSeeder = require('./e2e/support/data-seeder.js');
browser.params.dataSeeder = new DataSeeder();
browser.catchProtractorErrorInLocation = (error, location) => {
throw new Error(`Error in ${location}\n ${error}`);
};
browser.catchProtractorErrorInBeforeAll = (error) => browser.catchProtractorErrorInLocation(error, 'beforeAll()');
// Return a promise that resolves when DataSeeder is connected to service and ready to go
return browser.params.dataSeeder.waitForConnect();
},
With that in place, we can easily do beforeAll() setup code in an abbreviated set of lines.
beforeAll(() => {
return browser.params.dataSeeder.createTestUsers()
.catch(browser.catchProtractorErrorInBeforeAll);
});
You obviously need to do different things in your setup, but you can see how the pattern can apply.