50

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', () => { }); }); }); 
12
  • 4
    Put the assignment inside the it? Commented May 23, 2017 at 17:19
  • 1
    Or use an immutable structure that you pass (implicitly or explicitly) into your tests, instead of relying on let Commented May 23, 2017 at 17:19
  • 3
    Also take a look at github.com/stalniy/bdd-lazy-var Commented Jun 12, 2018 at 17:16
  • 4
    It's been a while since I've messed with this. What's at stake is code duplication. With the rspec let way of doing things, you can avoid calling setupSomeThing in every single "it" block. Or separate before blocks in each describe block. With one function, it's somewhat trivial to do without the rspec-style lets. But if the setup requires 8 different inputs, some of which need to change between contexts and some that don't, it's much easier to just have that setup code once in a before block that uses "let" variables that are changed depending on the context. Commented May 14, 2019 at 21:50
  • 3
    It ends up cleaning up your test code substantially. The lets act as declarative control switches to hit different branches of the code under test. This reduces the test to clean blocks specifying what the switches are (essentially specifying what logical branches to go through) followed by a set of it blocks describing all of the things that should happen given that set of lets. Commented May 14, 2019 at 21:58

4 Answers 4

16

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', () => { }); }); }); 
Sign up to request clarification or add additional context in comments.

1 Comment

My biggest peeve is losing variable typing that helps autocomplete. You can add js docs to help give documented typing which may help some. ie /** @type {string} */ unfortunately this may not work with complex typing such as generics :(
5

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

Comments

0

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.

Comments

0

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') }) ... 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.