Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSpec let! equivalent proposal #113

Open
mvgijssel opened this issue Feb 9, 2021 · 7 comments
Open

RSpec let! equivalent proposal #113

mvgijssel opened this issue Feb 9, 2021 · 7 comments

Comments

@mvgijssel
Copy link

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?

@stalniy
Copy link
Owner

stalniy commented Feb 9, 2021

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.

@stalniy
Copy link
Owner

stalniy commented Feb 9, 2021

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

@newhouse
Copy link

newhouse commented Feb 9, 2021

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 beforeAlls?

@mvgijssel
Copy link
Author

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 beforeAlls?

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.

@newhouse
Copy link

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...

@rinslow
Copy link

rinslow commented Jul 12, 2021

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 }

@Ymaril
Copy link

Ymaril commented Nov 23, 2022

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)
    });
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants