worker icon indicating copy to clipboard operation
worker copied to clipboard

Worker Test Example

Open williscool opened this issue 5 years ago • 7 comments

Was looking through the code for how to test as its not really clear from the readme. Here is something I hacked up.

Would appreciate any feedback on it. Once its decent I would be happy to make a pr. This example is for jest with @babel/preset-env

Thanks for the great lib!

// tasks/hello.js
export default async (payload, helpers) => {
  const { name } = payload;
  helpers.logger.info(`Hello, ${name}`);
};
// test/tasks/hello.test.js
import hello from '../../src/tasks/hello';
import config from 'config'; // 
import { runOnce, quickAddJob } from 'graphile-worker';

const connectionString = config.get('pg_db_url'); // i.e. "postgres://user:pass@host:port/db?ssl=true"
const payloadName = 'Bobby Tables';

// based on https://github.com/graphile/worker#quickstart-library
describe('hello task', () => {
  it('does things', async (done) => {
    const spy = jest.spyOn(global.console, 'info');
    // Or add a job to be executed:
    const job = await quickAddJob(
      // makeWorkerUtils options
      { connectionString },

      // Task identifier
      'hello',

      // Payload
      { name: payloadName }
    );

    console.log(job);
    expect(job).toBeTruthy();

    // Run a worker to execute jobs:
    // eslint-disable-next-line
    const runner = await runOnce({
      connectionString,
      concurrency: 5,
      // Install signal handlers for graceful shutdown on SIGINT, SIGTERM, etc
      noHandleSignals: false,
      pollInterval: 1000,
      // you can set the taskList or taskDirectory but not both
      taskList: {
        hello,
      },
      // or:
      //   taskDirectory: `${__dirname}/tasks`,
    });

    // until https://github.com/graphile/worker/issues/28 is resolved with events
    // or i write my own event emmitter. we'll just test the logger
    //
    // this also is ok https://github.com/bpedersen/jest-mock-console
    // but i don't want to swallow logs this early in teh dev cycle
    //
    // https://stackoverflow.com/a/56677834/511710
    // https://jest-bot.github.io/jest/docs/expect.html#expectstringcontainingstring

    /**
     * graphile-worker's build in logger passes console calls like this
     * 
     * 1: "[%s%s] %s: %s", "job", "(worker-e03827077c435f77f5: hello{15})", "INFO", "Hello, Bobby Tables"
     * 2: "[%s%s] %s: %s", "worker", "(worker-e03827077c435f77f5)", "INFO", "Completed task 15 (hello) with success (20.03ms)"
     * 
     */
     expect(spy).toHaveBeenCalledWith(
      expect.anything(),
      expect.anything(),
      expect.anything(),
      expect.anything(),
      expect.stringContaining(payloadName)
    );

    done();
  });
});

williscool avatar Aug 06 '20 02:08 williscool

I don’t have time to review this today; but you might like to look at the tests in Graphile Starter.l, a few of those involve worker methinks.

benjie avatar Aug 06 '20 06:08 benjie

Since the worker tasks are just functions, do you actually want to test the DB infrastructure around them or do you just want to test the function itself? It seems that we could expose makeJobHelpers then you could do something like:

it('does things', async (done) => {
  const client = /* get client from pool */
  const withPgClient = (cb) => cb(client);
  const job = mockJob({task_identifier: 'hello', payload: {...payload...}})
  const helpers = makeJobHelpers({...options...}, job, {withPgClient});

  const spy = jest.spyOn(global.console, 'info');
  await hello(job.payload, helpers);
  expect(spy.mock.calls[0][4].includes(payloadName)).toBeTruthy();
});

I think it makes sense to include mockJob as part of graphile-worker because the Job definition may change over time. We can apply defaults to most of the fields, the only required one would be task_identifier.

What do you think?

benjie avatar Aug 11 '20 09:08 benjie

yeah all i want to test is that given a certain payload to a task the things I want it to do are done.

I want the db stuff as extracted away as possible.

also I had a lot of flakiness in my tests until I added DROP SCHEMA graphile_worker CASCADE; to a beforeEach on all of my tests that use the workers. has been really solid ever since

williscool avatar Aug 12 '20 02:08 williscool

You could just do delete from graphile_worker.jobs; I think; it might be more efficient time-wise.

benjie avatar Aug 17 '20 18:08 benjie

updated to waaaay better way of writting spy tests

    expect(spy).toHaveBeenCalledWith(
      expect.anything(),
      expect.anything(),
      expect.anything(),
      expect.anything(),
      expect.stringContaining(payloadName)
    );

williscool avatar Sep 03 '20 04:09 williscool

Just following up on this thread as I want to test one of my workers as well.

Did any tooling end up being exposed to support mocks so that the tests don't require a database connection? (I saw @benjie's thought about exposing makeJobHelpers but am not sure if that made it into the code base)

(Thank you for the great library!)

slifty avatar Nov 29 '23 22:11 slifty

makeJobHelpers is indeed not exposed; I think you would need to mock it anyway.

benjie avatar Dec 11 '23 12:12 benjie