Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing Jest unit tests

I'm trying to test a couple of database implementations using Jest. To help test these implementations, I first came up with a set of unit tests against an API that both implementations are expected to implement.

I'm currently struggling with passing the two implementations to the test suites.

Below is a (dummy) MongoDB implementation in its simplest form:

class MongoDB {
  async query () {
    console.warn(`This is a dummy function.`)
  }

  async connect () {
    // The real connect takes some time..instead we just simulate it
    await new Promise((resolve, reject) => {
      setTimeout(resolve, 300)
    })
  }
}

And here's a small snippet of my tests:

let db
beforeAll(async () => {
  db = new MongoDB()
  await db.connect()
  console.log(`mongoDB ready`)
})

async function testDB (db) {
  describe('Basic', async () => {
    test('Valid instance', async () => {
      expect(db).toBeTruthy()
      expect(db.query).toBeTruthy()
    })
  })
}

describe('Tests', async () => {
  console.log(`Running testDB`)
  testDB(db) // Have also unsuccessfully tried changing this to: return testDB(db)
})

My goal with this approach is to wrap all my tests inside the testDB function and simply call it with various implementations. For example, testDB(new MongoDB()) and testDB(new MemoryDB()) and so on.

This doesn't seem to work as expected, however. The above code results in an error stating that:

  ● Tests › Basic › Valid instance

    expect(received).toBeTruthy()

    Expected value to be truthy, instead received
      undefined

The order of the console.log statements seems to suggest that the tests are running before db was initialized.

  console.log mongo.test.js:20
    Running testDB

  console.log mongo.test.js:7
    mongoDB ready

This entire example along with the resulting output can be reproduced on repl.it.

How can I reuse unit tests to test multiple implementations without resorting to duplicating the tests and maintaining two versions?

like image 924
Guru Prasad Avatar asked Jul 16 '18 22:07

Guru Prasad


1 Answers

Faced same need today. Here is the way, adapted from typescript, but you get the idea:

// common/service.test.js
export const commonServiceTests = (name, impl) => {
  describe(`Common tests for ${implName}`, () => {
    // pile your tests here
    test('test1', () => { ... });
    test('test2', () => { ... });
    test('test3', () => { ... });
  });
}

// just to avoid warning, that no tests in test file
describe('Common tests for CommonService implementations', () => {
  test('should be used per implementation', () => {});
});

And for your each implementation:

// inmemory/service.test.js
import { commonServiceTests } from '../common/service.test';
import ...; // your implementation here

const myInMemoryService = ...; // initialize it

commonServiceTests('InMemory', myInMemoryService);

Then all tests defined in common/service.test.js will be executed within each implementation test.

In case your initialization is async (which is most likely), then your shared tests should be async as well. Then:

// common/service.test.js
export const commonServiceTests = (name, impl: Promise) => {
  describe(`Common tests for ${implName}`, () => {
    // pile your async tests here
    test('test1', async () => {
      const svc = await impl;
      return await svc.doSomthingPromisy();
    });
    test('test2', () => { ... });
    test('test3', () => { ... });
  });
}
like image 105
muradm Avatar answered Oct 02 '22 13:10

muradm