Jest - Get Test and Description Name [duplicate] - javascript

This question already has answers here:
Get the current test/spec name in Jest
(4 answers)
Closed 7 months ago.
I am trying to get the name of the test name that was passed into it(name: string) Jest function, as well as the description name that was passed into describe(name: string, ....).
Is there a way to access this somehow?
E.g.
describe("Description", () => {
it("Test", () => {
const description = "How do I get the description name?"
const test = "How do I get the test name?";
expect(`${description} - ${test}`).toBe("Description - Test");
});
})
CodeSandbox Link
Update 1
Underlying reason why I want this is because I want to do some custom snapshot naming conventions based on describe, test, and some environment variables (e.g. viewport sizes + mock/real data).

You can achieve this using a custom matcher
expect.extend({
testName(exp) {
return {
pass: this.currentTestName === exp,
message: () => '',
};
},
});
describe("Description", () => {
it("Test", () => {
expect(`Description Test`).testName();
});
});

Perhaps, the most obvious solution is what you want. Instead of passing literals to describe and it just store them in a respective constants which could then be easily accessed inside the respective test cases though the outer scope.
These constants could be easily initialized either by values from the environmental variables or by literal defaults.
const suiteA = 'Description';
describe(suiteA, () => {
const testA = 'Test';
it(testA, () => {
expect(`${suiteA} - ${testA}`).toBe("Description - Test");
});
})

Related

When to use curly brackets vs curved brackets in React

I'm currently taking an online course to learn React and I'm confused as to when I should be using { vs (.
I have 3 files:
App.js
const App = () => {
card-list.component.jsx
const CardList = ({ monsters }) => (
card.component.jsx
const Card = ({ monster }) => {
This is the code that currently works. Notice that on the second code the last character used is (. I thought of changing it to { to make it consistent with the other files but somehow the card list no longer shows up on the page although I didn't get any compile errors.
Can someone explain this to me and tell me when I should use one over the other?
This essentially is a feature of arrow functions in js.
const myArrowFunc = ({key1}) => ("Hello there! " + key1);
is essentially the same as
const myArrowFunc = ({key1}) => { return "Hello there! " + key1 };
when you leave out the curly brackets, the return is implicit.
When you include the curly brackets, you must explicitly use the return statement.
const someObj = { key1: "value1" };
const someReturn = myArrowFunc(someObj);
console.log(someReturn); // logs "Hello there! value1" to the console
()=>{} is the syntax of arrow function
When an arrow function only contains one line that return a value, e.g.:
() => {
return 1;
}
it can be simplified to
() => 1
Now what if you want to return an object directly? i.e. how to simplify
() => {
return { foo: "bar" }
}
Since an object also use {}, you cannot write {foo: "bar"} directly after the arrow as it will be treated as the function body. Wrapping the object within () solves the problem since a () chunk must be an expression. The above example can be simplified to
() => ( { foo : "bar" } )

Change process.env variable for single test using JEST [duplicate]

This question already has answers here:
how to reset module imported between tests
(2 answers)
Closed last year.
i have the following code in a file that i'm trying to test:
foo.js
let localEnv = (process.env.LOCAL_ENVIRONMENT).toLowerCase() === 'prod' ? 'prod' : 'stage';
currently, i set this value using setupFiles which points to env.js and it contains:
process.env.LOCAL_ENVIRONMENT = 'prod';
my question is, how do i change process.env.LOCAL_ENVIRONMENT to test (or anything else) in foo.test.js? Just need to do this in a single test, so this line will be covered in my test coverage.
I tried doing something like this but it didn't work...
foo.test.js
test('Nonprod', async () => {
process.env.LOCAL_ENVIRONMENT = 'test';
...
});
You can alter the value as you tried. However you have to take into account that in your original file you only access the variable when you first load this script. In order to make this work you have to do sth like this:
// in your test file foo.test.js
const prev = process.env.LOCAL_ENVIRONMENT
process.env.LOCAL_ENVIRONMENT = 'test'; // from now on the env var is test
const myModule = require('./foo.js'); // foo.js is executed and the var is read as test
process.env.LOCAL_ENVIRONMENT = prev; // change value back
This has some caveheats as you cant test multiple scenarios with this (as the module is only loaded in once).
If you want to test more scenarios you have multiple options:
One would be to split the logic and process.env.LOCAL_ENVIRONMENT apart, for example
function getLocalEnv(env = process.env.LOCAL_ENVIRONMENT) {
return env.toLowerCase() === 'prod' ? 'prod' : 'stage';
}
This function is now very easy to test and doesn't depend on env vars for that anymore
I found the answer on how to reset and re-require the tested module here:
how to reset module imported between tests
The most easier way to test it for a specific test case is to set the test into a describe scope and to apply / remove the env value in beforeAll / afterAll or beforeEach / afterEach hooks depending on your needs.
describe('Test example', () => {
describe('prod (default)', () => {
test('do the thing', () => {
doTheThing(); // process.env.LOCAL_ENVIRONMENT is by default 'prod' because of your setupFiles
});
});
describe('test env', () => {
const oldEnv = process.env.LOCAL_ENVIRONMENT; // or { ...process.env } to copy the whole env
beforeAll(() => {
process.env.LOCAL_ENVIRONMENT = 'test';
});
afterAll(() => {
process.env.LOCAL_ENVIRONMENT = oldEnv; // do not forget to do this
});
test('do the thing', () => {
doTheThing(); // process.env.LOCAL_ENVIRONMENT is 'test' here
});
});
});

Using Jest's test.each parameterized test variable scope

If I have a spec file like this:
let a;
beforeEach(() => {
a = 'hello';
})
describe('my test suite', () => {
test.each([
[a, 'hello']
])(
'testing %s with expected result %s',
(myVariable, expectedResult) => {
expect(myVariable).toBe(expectedResult);
})
});
I get an error that a is undefined in the parameterized table. If I use a regular test method I have access to a.
You did forget the closing bracket on the beforeEach() line.
let a;
beforeEach(() => {
a = 'hello';
} );
You also have i% and %1 which is for integers, and you want strings (%s).
With only one test, you do not need the beforeEach() and can simply do:
const a:string = 'hello';
test.each([[a, 'hello']])(
'.compare(%s, %s)',
(myVariable, expected) => {
expect(myVariable).toBe(expected);
},
);
However, I cannot get this to work either. I can reference the variable directly in the test, such as:
const a:string = 'hello';
test.each([[a, 'hello']])(
'.compare(%s, %s)',
(myVariable, expected) => {
expect(a).toBe(expected);
},
);
Using your myVariable will not get the value from a inside the closed loop of the test. Literals do work though. The beforeEach would defeat the purpose of setting a value there, as it would not need to be changed in the middle of the test.each() since this is meant to run the same test with different data. You can still create objects and other required things in your beforeEach, and reference them directly (my a variable), but the test data that changes for each run does not seem to get the value from the outside loop.

ES6 Template Literals: How to pass a scope before they are interpreted?

I am starting to use template literals to make a error generator.
I have working code, but I am forced to declare the list of possible errors inside the constructor scope, and I am not pleased with that.
Is there a way to either copy a template literal without evaluating it so I can evaluate it in the right scope? Or pass the scope to the template literal?
Working error.js:
'use strict';
class Error {
constructor(code) {
const error = {
//...
//API
1001: 'No token',
1002: `${arguments[1]}`,
1003: `${arguments[1]} ! ${arguments[2]}`,
1004: 'Missing data'
//...
};
let i = 0;
this.code = code;
this.error = error[code];
//...
}
}
// export default Error;
module.exports = Error;
Called like:
'use strict';
const Error = require('./error.js');
console.log(new Error(1002, 'var'));
What I would like is to be able to declare const error in the module scope, or better yet, in it's own file that I require. But doing so right now lead to argument not being the ones of the constructor, but the one of the module.
String literals are evaluated immediately. They cannot be used as templates to be formatted later (Unlike for example Python's format strings that look similar).
You could do what Leonid Beschastny suggests and use little functions that does the interpolation for you.
Something like this:
const error = {
1001: () => 'No token',
1002: (args) => `${args[1]}`,
1003: (args) => `${args[1]} ! ${args[2]}`,
1004: () => 'Missing data'
};
this.error = error[code](arguments);
It's a darn shame that template literals aren't more flexible.
Here are two methods that may be close to what you want.
First:
var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'
To generalify:
var s = (<variable names you want>) => {return `<template with those variables>`}
If you are not running E6, you could also do:
var s = function(<variable names you want>){return `<template with those variables>`}
This seems to be a bit more concise than the previous answers.
https://repl.it/#abalter/reusable-JS-template-literal
Second
class Person
{
constructor (first, last)
{
this.first = first;
this.last = last;
}
sayName ()
{
return `Hi my name is ${this.first} ${this.last}`;
}
}
var bob = new Person("Bob", "Jones")
console.log(bob.sayName()) // Hi my name is Bob Jones
console.log(new Person("Mary", "Smith").sayName()) // Hi my name is Mary Smith
https://repl.it/#abalter/late-evaluation-of-js-template-literal
My preferred solution to pass scope is using this wrapper:
function defer([fisrt, ...rest]) {
return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, fisrt);
}
That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:
> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable'); // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null); // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'
Applying this tag returns back a 'function' (instead of a 'string') that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes 'undefined'.
You can find more information in those other answers: this and that.

Javascript/Typescript 'this' scope

I am working with Ionic2 and Meteor. I do however have a Javascript/Typescript issue relating to the scope of the this object.
I have read that I should use bind when I don't have handle on this at the appropriate level.
I probably don't understand the concept, because I try the following, but get an error trying to call a function.
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
setLocalMessage.bind(message);
});
});
});
and
private setLocalMessage(message: Message): void {
this.localMessageCollection.insert(message);
}
I get the following error when I try build the app:
ERROR in ./app/pages/messages/messages.ts
(72,19): error TS2304: Cannot find name 'setLocalMessage'.
UPDATE
Thank you for the advise below.
I am now using the following, and it works.
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
});
I have read that I should use bind when I don't have handle on this at the appropriate level.
That's a bit outdated now, better have a look at How to access the correct `this` context inside a callback? these days which also shows you how to use arrow functions.
You're getting the error message because setLocalMessage is not a variable but still a property of this so you have to access it as such. There are basically three solutions in your case:
bind
messageData.find().forEach(this.setLocalMessage.bind(this));
the context argument of forEach (assuming it's the Array method):
messageData.find().forEach(this.setLocalMessage, this);
another arrow function:
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
There are a few things wrong here.
In ES6 (and thus TypeScript), you need to refer to instance members using explicit this, such as this.setLocalMessage. Just writing setLocalMessage is invalid no matter where the code is.
Inside a function, the this object will probably not be what you expect anyway. You need to capture the this object from outside the function and put it in a variable, like so:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let self = this;
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
self.setLocalMessage(message);
});
});
});
Alternatively, you can use an arrow expression, in which this is the same as what it is in the code around it:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(message => this.setLocalMessage(message));
});
});
});
It's not an issue of TypeScript itself. Without it, the code will just fail at runtime.

Categories

Resources