RSpec let! equivalent proposal
Opened this issue · 7 comments
The RSpec testing framework has lazy variable evaluation using let(:some_var) { ... }
similar as this libary def("someVar", () => ...)
. But there's also an additional helper let!(:other_var) { ... }
(note the exclamation mark) which will evaluate the variable, despite it being referenced in an example or not.
This comes in handy when a spec requires to setup some data which is not directly referenced. I'm currently doing to following for variables that need instantiation:
def("foo", () => "foo");
def("someBackgroundThing", () => "bar");
subject(() => someMethod($foo))
beforeEach(() => {
$someBackgroundThing; // <---- referencing here so it's available
})
it("works", () => {
...
});
My proposal would be to introduce deff
(or something similar) to replicate the "always instantiatie" behavior.
What do you think?
This is interesting. I see only one issue. this library allows to use lazy variables in beforeAll
and afterAll
hooks.
But for this specific helper, I can actually say that the behavior is different (anyway it's different), so this is fine.
However, I plan to rewrite library API to something like this. This will allow to greatly simplify integration with testing frameworks.
But not sure when this happens. Don't have enough capacity
I am +1 for this, but could live without it, too.
RSpec also has before/after
and beforeAll/afterAll
equivalents (before(:each)
/before(:all)
) as I'm sure you're aware, yet still allows for the non-lazy evaluation using let!(:varname) { "value here" }
A def!
function (that could cause a definition be instantiated/evaluated whether it was used or not) may have very few instances (or perhaps none?) that can't be solved with referencing it in a before/beforeAll
block, but nonetheless it would be helpful, and make this library "feel" even more like "RSpec for Javascript".
Scattered references I found regarding let!
make it sound like RSpec evaluates all let!
s in an "implicit before block". Not sure if that's a before(:each)
or before(:all)
block...but my gut says it would be a before(:all)
?
If bdd-lazy-var
is already able to tap into the lifecycle hooks and add a before
or beforeAll
hook that will evaluate all def!
defined things, I think that's the move. I suppose it would need/want to happen before user-defined before
or beforeAll
s?
I am +1 for this, but could live without it, too.
RSpec also has
before/after
andbeforeAll/afterAll
equivalents (before(:each)
/before(:all)
) as I'm sure you're aware, yet still allows for the non-lazy evaluation usinglet!(:varname) { "value here" }
A
def!
function (that could cause a definition be instantiated/evaluated whether it was used or not) may have very few instances (or perhaps none?) that can't be solved with referencing it in abefore/beforeAll
block, but nonetheless it would be helpful, and make this library "feel" even more like "RSpec for Javascript".Scattered references I found regarding
let!
make it sound like RSpec evaluates alllet!
s in an "implicit before block". Not sure if that's abefore(:each)
orbefore(:all)
block...but my gut says it would be abefore(:all)
?If
bdd-lazy-var
is already able to tap into the lifecycle hooks and add abefore
orbeforeAll
hook that will evaluate alldef!
defined things, I think that's the move. I suppose it would need/want to happen before user-definedbefore
orbeforeAll
s?
That could be an approach! If would expect it to be before(:each), as different describe/context sections are able to override the variable as well.
Yeah, my RSpec is getting a little rusty, but before(:each)
=> beforeEach
probably makes sense. For some reason I was thinking that the values persisted across test examples, but I don't think so...
I would like the equivalent of let!
as well.
Syntax suggestion: a regular def
with {eager: true}
in its options or my preferred: {lazy: false}
My use case is
beforeEach(async () => {
await SomeSequelizeService.create($params); // This line returns an ORM object, I would like to save it's id.
});
Not all methods in my context
needs the id
of the object or even the object at all, because they are testing other side effects of the call.
In ruby I would just
let!(:existing_object) { create :object}
let(:existing_object_id) { (existing_object.id }
I often have to work with async functions. And this forces me to write a lot of await statements.
describe("Board", function () {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
def('contractFactory', async () => await ethers.getContractFactory("Board"));
def('owner', async () => await ethers.getSigner());
def('startTime', async () => await time.latest());
def('unlockTime', async () => (await $startTime) + ONE_YEAR_IN_SECS);
def('lockedAmount', () => ONE_GWEI);
subject(async () => {
return await (await $contractFactory).deploy($unlockTime, { value: $lockedAmount });
});
describe("Deployment", function () {
const TWO_YEAR_IN_SECS = 365 * 24 * 60 * 60;
def('unlockTime', async () => (await $startTime) + TWO_YEAR_IN_SECS);
it(async () => {
expect(await (await $subject).unlockTime()).to.equal(await $unlockTime)
});
});
});
It was possible to do without some async/await. I added them for clarity
It would be great if the variables were waiting for the promise to complete. But with lazy variables this is obviously not possible(#5). But it is possible with let! variables.
I suggest that when declaring a variable, add the optional ability to specify which variables must exist before this variable function is executed. And they are passed to function as parameters.
def!('thirdVarName', (firstVar, secondVar) => firstVar + secondVar, ['firstVarName', 'secondVarName']);
You can also specify lazy variables in this list. They are simply initialized as non-lazy along with this variable
Thus, even before starting the test, it will be possible to disassemble in the order of initialization of variables. And at the same time keep the var overriding in child describes
Example
describe("Board", function () {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
def!('contractFactory', () => ethers.getContractFactory("Board"));
def!('owner', () => ethers.getSigner());
def!('startTime', () => time.latest());
def('unlockTime', (startTime) => startTime + ONE_YEAR_IN_SECS, ['startTime']);
def('lockedAmount', () => ONE_GWEI);
subject!((contractFactory, unlockTime, lockedAmount) => {
return contractFactory.deploy(unlockTime, { value: lockedAmount });
}, ['contractFactory', 'unlockTime', 'lockedAmount']);
describe("Deployment", function () {
const TWO_YEAR_IN_SECS = 365 * 24 * 60 * 60;
def('unlockTime', (startTime) => startTime + TWO_YEAR_IN_SECS, ['startTime']);
it(async () => {
expect(await $subject.unlockTime()).to.equal($unlockTime)
});
});
});