DEV Community

Discussion on: Jest and recurring actions

Collapse
peerreynders profile image
peerreynders

From what I can tell uvu's context is simply a Suite Fixture, which started out as an immutable shared fixture but is now just a shared fixture—i.e. a fixture belonging to the suite that is shared by all of the suite's tests.

However there are lots of fixture styles independent of what any one tool may directly support:

Fresh Fixture Setup: Each test constructs its own brand-new test fixture for its own private use. When teardown (afterEach) is required it's a Persistent Fresh Fixture.

  • In-line Setup: Each test creates its own fresh fixture constructing everything in-line within the test.
  • Delegated Setup: Each test creates its own fresh fixture using suite scoped creation functions.
    • Creation Method: Set up the test fixture by calling functions that hide the mechanics of building ready-to-use "values" behind intent-revealing names.
  • Implicit Setup: Build the test fixture common to several tests in the setUp method (beforeEach in Jest).

Shared Fixture Construction: Reuse the same instance of the test fixture across many tests.

  • Prebuilt Fixture: The shared fixture is built separately from the tests (usually in beforeAll).
  • Lazy Setup: Use lazy initialization of the fixture to create it in the first test that needs it. (Only needed when beforeAll isn't supported)
  • Suite Fixture Setup: Build/destroy the shared fixture in special functions (beforeAll,afterAll) called by the Test Automation Framework before/after the first/last test is called.
  • Setup Decorator: Wrap the test suite with a Decorator that sets up the shared test fixture before running the tests and tears it down after all the tests are done.
  • Chained Tests: Let other tests in a test suite set up the test fixture.

Also

So in Jest/Vitest the Suite Fixture looks something like this:


describe('User suite', () => {
  let context = initialContext();

  beforeAll(async () => {
    context.client = await db.connect();
  });

  beforeEach(async () => {
    context.user = await context.client.insert(
      'insert into users ... returning *'
    );
  });

  afterEach(async () => {
    await context.client.destroy(
      `delete from users where id = ${context.user.id}`
    );
    context.user = undefined;
  });

  afterAll(async () => {
    context.client = await context.client.end();
  });

  test('valid user present', () => {
    expect(context.user).toBeDefined();
    expect(context.user).toHaveProperty('id');
    expect(context.user.id).toBeGreaterThan(0);
  });

  // more tests ...

});
Enter fullscreen mode Exit fullscreen mode

One could argue that context.client is a shared fixture while context.user is a fresh fixture. I think that uvu's api style made context a necessity to simplify shared fixtures. Jasmine always gets criticised for its polluting globals; Jest preserved that style and all of the suite's tests are injected with a single function - a closure that can hold context across the suite's tests.

uvu adopted a less verbose, more modern api style that avoids polluting globals. suite() creates a Suite callable object that contains all the necessary methods to incrementally configure and add tests to the suite prior to running. So in the absence of that overarching closure of a Jasmine/Jest suite, the context parameter threads a shared fixture throughout a suite's tests. In my judgement uvu's approach is cleaner though I wouldn't be surprised if some people prefer the Jasmine/Jest approach.