简体   繁体   中英

Jest - import multiple tests in a describe block, reusing variables defined in beforeEach()

I am familiar with RSpec where it is very easy to reuse test cases by writing shared examples

shared_example_for 'a cute pet' do 
  it 'tests that the pet is a small' { expect(pet.size).to be_lesser_than(10) }
  it 'tests that the pet can smile' { expect(pet.can_smile?).to be }
end

describe 'The Octocat' do
  let(:pet) { Octocat.new }

  it_behaves_like 'a cute pet'
end
...
describe 'The Doge' do 
  let(:pet) { Doge.new }

  it_behaves_like 'a cute pet'
end

Is there an equivalent in Jest ? Something that would let me reuse variables set in beforeEach() blocks ? I am trying to find a way using something like the following :

# __tests__/cuteness.js
export const cutenessTests = function() {
  test('it is small', () => {
    expect(petSetInBefore.length).toBeLesserThan(5)
  })
  test('it can smile', () => {
    expect(petSetInBefore.canSmile).toBe(true)
  })
}

# __tests__/famous_animals.test.js
import { cutenessTests } from './cuteness'

describe('Famous animals', () => {
  let petSetInBefore;

  describe('Octocat', () => {
    beforeEach(() => {
      petSetInBefore = new Octocat();
    })

    cutenessTests.bind(this)()
  })
})

The important here is that I am trying to share multiple test definitions and not just one, otherwise I could have passed the petSetInBefore to the shared function.

EDIT : each of my tests and nested describe are likely to alter my test environment and objects, so the beforeEach is used to restore a proper test environment. Here is a better example

class Octocat {
  get strokeFor(time) {
    this.strokeTime = this.strokeTime + time
    if (this.strokeTime <= 10) {
      this.mood = 'happy'
    } else {
      this.mood = 'bored'
    }
  }
}

class Doge {
  get strokeFor(time) {
    this.strokeTime = this.strokeTime + time
    if (this.strokeTime <= 5) {
      this.mood = 'happy'
    } else {
      this.mood = 'bored'
    }
  }
}

const cutenessTests = function() {
  describe('when stroked for a short while', () => {
    beforeEach(() => {
      petSetInBefore.strokeFor(1);
    })

    test('it is happy', () => { expect(petSetInBefore.mood).to(eq('happy')) }

    describe('when stroked too much', () => {
      beforeEach(() => {
        petSetInBefore.stroke(1000);
      })

      test('it gets bored', () => { expect(petSetInBefore.mood).to(eq('bored')) }
    })

    describe('when stroked a little longer', () => {
      beforeEach(() => {
        petSetInBefore.strokeFor(4);
      })

      test('it is still happy', () => { expect(petSetInBefore.mood).to(eq('happy')) }
    })
  })
}

EDIT2: Here is a repl.it based on Gui3's answer

EDIT3 : the object can be altered before or during the reusable tests

describe('Famous animals', () => {
  let petSetInBefore;

  describe('Octocat', () => {
    beforeEach(() => {
      petSetInBefore = new Octocat();
    })

    describe('when it is not well rested', () => { 
      beforeEach(() => { petSetInBefore.wellRested() } // Extra object preparation / context before calling reusable examples
      cutenessTests()
    }),
    describe('when it is not well rested', () => { 
      // Calling reusable examples without extra context
      cutenessTests()
    })
  })
})

You can simply move the shared tests into a function that does the it() calls.

class Octocat {
  get length() {
    return 3;
  }

  get canSmile() {
    return true;
  }
}

class GrumpyCat {
  get length() {
    return 1;
  }

  get canSmile() {
    return false;
  }
}

const behavesLikeAPet = (pet) => {
  it('is small', () => expect(pet.length).toBeLessThan(5));
  it('can smile', () => expect(pet.canSmile).toEqual(true));
};

describe('Famous animals', () => {
  describe('Octocat', () => {
    behavesLikeAPet(new Octocat());
  });

  describe('GrumpyCat', () => {
    behavesLikeAPet(new GrumpyCat());
  });
});

You will get detailed output for every it test:

Famous animals
  Octocat
    ✓ is small (2ms)
    ✓ can smile (1ms)
  GrumpyCat
    ✓ is small
    ✕ can smile (2ms)

If you still want beforeEach ,

for reasons ... it works if you declare your variable in the global scope

let petSetInBefore; // here it works
describe('Famous animals', () => {
  //let petSetInBefore; // here it's undefined

  describe('Octocat', ()  => {
    //let petSetInBefore; // undefined too

    beforeAll(() => {
      petSetInBefore = new Octocat();
    })

    cutenessTests() // .bind(this) results the same
  });

  describe('Doge', () => {
    beforeEach(() => {
      petSetInBefore = new Doge();
    })

    cutenessTests.bind(this)()
  });
})

https://repl.it/@gui3/jestSharedTests

seems like the tests inside the shared function cannot share variables from beforeEach otherwise ...

Jest has describe.each(table) which I haven't seen being used a lot, but it's really helpful for reusing tests which have common/same results.

In case for identical expectations for both of the test subjects you can do it like this:

const aCutePet = pet => {
  it("should be small", () => {
    expect(pet.size).toBeLessThan(10);
  });

  it(`should be able to smile`, () => {
    expect(pet).toHaveProperty('can_smile', true)
  });
}

describe.each([
  [new Doge],
  [new Octocat]
])("The %O", aCutePet);

The output:

  The Doge { size: 3, can_smile: true }
    ✓ should be small (1ms)
    ✓ should be able to smile (1ms)
  The Octocat { size: 5, can_smile: true }
    ✓ should be small
    ✓ should be able to smile (1ms)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM