Jest equivalent to RSpec lazy evaluated variables (let)?

Viewed 4898

In rspec you can do something like this:

let(:input) { 'foo' }
before_each do
   setup_some_thing(input)
end

context 'when input is bar do
  let(:input) { 'bar' }
  it 'does something different' do
  end
end

context 'when input is baz do
  let(:input) { 'baz' }
  it 'does something else different' do
  end
end

This allows you to define a method call or instantiation of a large object as a sum of its smaller parts. You can then override those individual small parts inside different contexts. The idea being that you create a happy path before each test, and then specify deviations from the happy path within your context blocks.

Unfortunately, I can't seem to do this with Jest. I've tried the following:

beforeEach(() => {
  let input = 'foo';
  beforeEach(() => {
    setupSomeThing(input);
  });

  describe('when input is bar', () => {
    input = 'bar';
    it('does something different', () => {

    });
  });

  describe('when input is baz', () => {
    input = 'baz';
    it('does something different', () => {

    });
  });
});

Because jest executes every describe block before running any specific describe block, input is always 'baz'. Does anyone know a work around, or a way to get the rspec behavior?

Thanks in advance!

Update

You can get similar behavior (albeit without lazy evaluation) using beforeAll.

beforeEach(() => {
  let input = 'foo';
  beforeEach(() => {
    setupSomeThing(input);
  });

  describe('when input is bar', () => {
    beforeAll(() => {
     input = 'bar';
    });

    it('does something different', () => {

    });
  });

  describe('when input is baz', () => {
    beforeAll(() => {
     input = 'baz';
    });        

    it('does something different', () => {

    });
  });
});
4 Answers

The best solutions I've found have been libraries like

https://github.com/stalniy/bdd-lazy-var

and

https://github.com/tatyshev/given2

If you don't want to introduce a dependency, you can get similar behavior (albeit without lazy evaluation) by doing something like this:

beforeEach(() => {
  let input = 'foo';
  beforeEach(() => {
    setupSomeThing(input);
  });

  describe('when input is bar', () => {
    beforeAll(() => {
     input = 'bar';
    });

    it('does something different', () => {

    });
  });

  describe('when input is baz', () => {
    beforeAll(() => {
     input = 'baz';
    });        

    it('does something different', () => {

    });
  });
});

Use a beforeAll to set variables in a parent beforeEach.

Building on @Noah's own answer, I thought I would share our final solution with using this as well.

describe( "Logging in", () => { 
  beforeEach( () => login( this.password ) ); // Steps are same for all so re-use but use different password.

  describe( "when password is incorrect", () => {
    beforeAll( () => this.password = "INCORRECT PASSWORD" );

    it( "doesn't log in.", () => {
      // Your assertion(s).
    } );
  } );

  describe( "when password is correct", () => {
    beforeAll( () => this.password = "CORRECT PASSWORD" );

    it( "logs in successfully.", () => {
      // Your assertion(s).
    } );
  } );
} );

I’ve recently created my own library similar to the other ones mentioned here to achieve this exact purpose, as I felt both the other ones mentioned here had a few shortcomings. Check it out!

https://github.com/enova/givens

The only difference between this and Rspec is that it doesn’t actually put the variable on the scope, instead having you access it on the given function (which is why I didn’t want to use bdd-lazy-var)

The advantage of this library over given2 is that it fully supports caching across beforeEach and afterEach calls for a given test. If you have to interact with a lazily declared variable in a beforeEach block to do some set up, that set up will be preserved for the actual test, and the afterEach block after, before being cleared for the next test.

There is an npm module dedicated to that: https://www.npmjs.com/package/jest-rspec-style

The code would look like

describe('Hoge', () => {
  lazy('hoge', () =>
    new Hoge({ value: lazy('value') })
  )

  describe('#call', () => {
    subject(() =>
      lazy('hoge').call()
    )

    lazy('value', () =>
      'hoge-value'
    )

    it('Return value', () => {
      expect(subject()).toEqual('hoge-vale')
    })

    ...
Related