DEV Community

Discussion on: Jest and recurring actions

Collapse
 
jakecarpenter profile image
Jake Carpenter • Edited

I highly recommend against setting up or "seeding" data in shared setup and/or context. Your example of using the before/after actions to set up the database is an excellent use of them. However, the one case of beforeEach() to seed common data is where it can go too far and lead to confusing tests in the future.

It always begins with an innocent case of putting one record in the "context" so each test can assume is there, but before long the following will happen:

A new user is seeded with specific circumstances for a single new test

  1. That user meets the needs of another set of tests, so it is reused across 3 new tests
  2. A requirement changes and the first test needs to be adjusted, so the seeded data is changed
  3. Now the other 3 tests which used that seeded user are failing, having never intended to be changed

Unfortunately, now the 3 failing tests need to be changed but they did not make the data they needed clear within the test - instead relying on shared setup. This can make for a difficult task of reconciling data requirements for each and adding new seed data.

The best way to solve this is to skip the beforeEach approach of pre-seeded data and instead perform that logic in each test, creating any helper functions needed to make that as easy and clear as possible. Tests can then look like this and be much more up-front about what circumstances they are testing:

test('user database has Chris', () => {
  addUserToDatabase(db, 'Chris');
  expect(db.user.hasName('Chris')).toBeTruthy();
});

test('user database doesnt have Thomas', () => {
  addUserToDatabase(db, 'arbitrary');
  expect(db.user.hasName('Thomas')).not.toBeTruthy();
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dailydevtips1 profile image
Chris Bongers

Lovely point Jake!
This was a bit of a hard one to "not overcomplicate", but still make clear.

In general I would never have this run in a specific test, rather a setup script that would not be modified and handle all the setup needed props.

In cases on specific beforeEach we would only handle mocked API's that are super specific to a function.

Collapse
 
peerreynders profile image
peerreynders • Edited

Beware of the Share - the counterpart to Don't Repeat Yourself.

In essence you strongly prefer Delegated Setup over Implicit Setup.

Implicit Setup does mention under when to use it:

"It can however lead to Obscure Tests caused by a Mystery Guest by making the test fixture used by each test less obvious. It can also lead to Fragile Fixture if all the tests in the class do not really need identical test fixtures."

What you are describing is Data Sensitivity.

A new user is seeded with specific circumstances for a single new test

It could be argued that the requirement for a "new user" should prompt the creation of entirely new suite, i.e. Testcase Class per Fixture or in this context "Suite per Fixture".

Your concern is well founded but I think this is another case where everybody's obvious and favourite "demonstration code" can actually be a smell in the real world. One could argue that microtests shouldn't be using a database at all and where is the repository anyway? Unfortunately it's an example that everybody seems to understand even when its real world utility can be somewhat questionable.

On the whole it doesn't invalidate the usefulness of the suite and test setup (beforeAll, beforeEach) and teardown (afterAll, afterEach) actions; it just can be difficult to find clear and short code examples that demonstrate their usefulness.