1

I'm using agenda for background jobs and I'm trying to test the define method to ensure 1.) agenda.define is called and 2.) a method inside of agenda.define (triggerSend) is called.

I've included the four files I'm using to make this test: 1.) the agenda job definition 2.) triggerSend utility function 3.) sinon stubs 4.) the actual test. I will very much appreciate any and all help you can offer!

As is, agenda.define is being called twice and I can see logs for triggerSend being called twice. The first triggerSend shows a job object, the second triggerSend log shows undefined.

I would like to figure out how I can accurately capture agenda.define and triggerSend being called, using sinon.

agenda/campaigns/jobs/test.js (the job with agenda.define):

const triggerSend = require('../../../campaign/trigger.js');

module.exports = function(agenda) {
    agenda.define('test', function(job,done){
        console.log("agenda.define called!");
        triggerSend(job)
        .then(done).catch(done);
    });
}

campaign/trigger.js triggerSend utility function called in agenda.define:

module.exports = async function(job) {
    console.log("triggerSend for job: ", job);
    // Legacy procedural code
}

fixtures/stubs/index.js

const sinon = require('sinon');

module.exports = {
    DefineAgenda: function(agenda, jobMock) {
        return sinon.stub(agenda, 'define').callsFake(function(job, done) {
            /*
            *This is to call the function (job, done) callback from agenda.define,
            *it results in triggerSend being actually called (not the stub) but it
            *also results in agenda.define being called twice.
            */
            return arguments[1](jobMock, done);
        });
    },
    TriggerSend: function(triggerSend, jobMock) {
        return sinon.stub(triggerSend, 'default').callsFake(function(job) {
            console.log("triggerSend arguments: ", arguments)
            return arguments[1](jobMock);
        });
    }
}

test.test.js:

const chai = require("chai");
const expect = chai.expect;

const agenda = require('../../../modules/agenda').campaignInstance();
const triggerSend = require('../../../campaign/trigger.js');
const triggerSendObj = { default: triggerSend };

const DefineAgenda = require('../../../fixtures/stubs').DefineAgenda;
const TriggerSend = require('../../../fixtures/stubs').TriggerSend;
const jobMock = require('../../../fixtures/mocks/jobs').campaign;

const testJob = require('../../../agenda/campaigns/jobs/test.js');

describe('Campaign agenda.define', function() {
    before(function() {
        this.DefineAgendaStub = DefineAgenda(agenda, jobMock);
        this.TriggerSendStub = TriggerSend(triggerSendObj, jobMock);
    });

    it('is called', async function() {
        //call agenda job for test
        testJob(agenda);

        //assertions
        expect(agenda.define.called).to.be.true;
        expect(triggerSendObj.default.called).to.be.true;
    })
});

Danny Beyrer
  • 51
  • 1
  • 7

1 Answers1

1

Capture agenda.define is easy, but capture triggerSend being called is not. It requires preparation.

I am agendajs user and this example on how I do it, using piece of your code that I modify a little bit to make it easier to test.

For this test, I use packages: mongodb-memory-server (in order for agenda to work, need mongodb server), mongodb (as mongo client), agenda, chai, sinon, and delay (need to make sure that expect run after additinal time).

There are 3 files in the directory:

  1. trigger.js, which provides async function definition that called when job run;
  2. test.js, which provides function that define 'test' job;
  3. trigger.test.js, which provide test spec to check whether agenda.define called or not, and whether async function inside trigger.js get called or not when 'test' job run.
// File: trigger.js
module.exports = {
  send: async () => {
    console.log('Trigger Send');
    return Promise.resolve(true);
  }
};
// File: test.js
const trigger = require('./trigger.js');

module.exports = function(agenda) {
  // Define a job name: test, as async job using done.
  agenda.define('test', function(job, done) {
      trigger.send(job).then(done);
  });
}
// File trigger.test.js
const { MongoMemoryServer } = require('mongodb-memory-server');
const { MongoClient } = require('mongodb');
const Agenda = require('agenda');
const { expect } = require('chai');
const sinon = require('sinon');
const delay = require('delay');

const testJob = require('./test.js');
const trigger = require('./trigger.js');

describe('Agenda test', function () {
  let mongoServer;
  let mongoClient;
  let sandbox;
  let agenda;

  before(async function () {
    mongoServer = new MongoMemoryServer();
    // Get mongo uri from memory server.
    const mongoUri = await mongoServer.getUri();
    // Get client.
    mongoClient = new MongoClient(mongoUri, {
      useUnifiedTopology: true,
    });
    // Connect to database.
    await mongoClient.connect();
    // Initiate agenda.
    agenda = new Agenda({
      mongo: mongoClient.db('agenda'),
    });
    // Initiate sandbox.
    sandbox = sinon.createSandbox();
  });

  after(async function () {
    await mongoClient.close();
    await mongoServer.stop();
    sandbox.restore();
  });

  it('define called', function () {
    const spyAgendaDefine = sandbox.spy(Agenda.prototype, 'define');
    // Define test.
    testJob(agenda);
    // Expect that spy agenda define get called.
    expect(spyAgendaDefine.calledOnce).to.equal(true);
    // To make sure that the defined job name: test.
    expect(spyAgendaDefine.args[0][0]).to.equal('test');
  });

  it('triggerSend run', async function () {
    const spyTriggerSend = sandbox.spy(trigger, 'send');
    // Job name test has define above.
    // Start agenda properly.
    await agenda.start();
    // Run test job now.
    await agenda.now('test');
    // Need to add next tick delay.
    await delay(5);
    // Stop agenda properly.
    await agenda.stop();
    // Expect trigger send get called once.
    expect(spyTriggerSend.calledOnce).to.equal(true);
  });
});

When I run it using mocha at terminal:

$ npx mocha trigger.test.js 


  Agenda test
    ✓ define called
Trigger Send
    ✓ triggerSend run


  2 passing (109ms)

109ms, quite quick, right? Start up mongodb server in memory will not slow you down. :D

Notes:

  • before function needed to run mongodb server, connect to mongodb server, initialize agenda properly, and initialize sinon sandbox.
  • after function needed to close mongodb connection, shutdown mongodb server, and restore sinon sandbox. This is needed in order test runner not to hang and wait for timeout.
Dharman
  • 30,962
  • 25
  • 85
  • 135
andreyunugro
  • 1,096
  • 5
  • 18