Getting our Cron Tests Passing in NestJs

Session IV of Building a Leetcode Contest Percentile Analyser Part II


I lost my initial notes from my fourth session 😭 😭 😭 because of the way I was updating the same article, so going forward I'm using a dedicated article for each session.

Right. Continuing my fourth session, I'm starting from a place where I have mocked the class housing our cron jobs, and I am in-between mocking the for my first test.

import { Test, TestingModule } from '@nestjs/testing';
import { TasksService } from './tasks.service';

const TasksServiceMock = {
  scrapeContestData: jest.fn().mockImplementation(() => {

describe('TasksService', () => {
  let tasksService: TasksService;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [
          provide: TasksService,
          useValue: TasksServiceMock,

    tasksService = moduleRef.get<TasksService>(TasksService);

  describe('weekly', () => {
    test('Feb 13 at 4:30AM, scrapeContestData TO BE called', () => {

        .spyOn(global.Date, 'now')
        .mockImplementationOnce(() =>
          new Date('2022-02-13T11:01:58.135Z').valueOf()

Mocking the service relied heavily on the example code nest provided. Yaaay for example code 🎉 🥳 .

While I quickly looked up an example online for how to mock, but considering how many times I did this in my past role you'd think I wouldn't need to look this up anymore 😅 .

Also, I'm specifying Feb 13 at 4:30AM as my mock and choosing to use a Date string to specify this, so I'm heading over to the ECMAScript Language Specification to cross-check that I'm using the right format, and afterwards, it'll be a couple of minutes of coding/debugging to get my first test passing.

First version of my test

    test('Feb 13 at 4:30AM, scrapeContestData TO BE called', () => {

        .spyOn(global.Date, 'now')
        .mockImplementationOnce(() =>
          new Date('2022-02-13T04:30Z').valueOf()

At this point the test looks fine... now we need to make it pass.

Debugging a failing test

A few moments later, apparently the test is not fine because I can't get it to pass using the specific date I'm testing for.

Looking again at what the documentation says about task scheduling, I'm pretty sure I'll need to set this up for the test to work properly.


But first, let's see if some fluke happens to make my test pass when I configure my cron job to run every second.

A few moments later, no fluke occurs. And as we can see in the image below the cron job is definitely running, but the test is not going green.


This means there's something wrong with our test and we need to update it.

Looking at NestJs codebase for an example

Uhh, so I went to the src code to see how nest tests their own cron package 🤷‍♂️

For starters it looks like I need an integration test that bootstraps the entire app for what I want to test. I can see that the nest codebase initializes an application in each test and keeps track of the calls made by the cronService in the cronService class itself, so I'm going to adopt this pattern.

  it(`should schedule "cron"`, async () => {
    await app.init(); /**This is where they initialize the app**/

export class CronService {
  callsCount = 0; /**They keep track of cron count here**/
  dynamicCallsCount = 0;

Our first passing test

And so with a few modifications we have our first passing test 🥳 🥳 🥳 🥳 🥳 🥳


Of course we can't stop here lol. To get this test passing, we're calling the cronService every second. Let's add a failing test that forces us to be more specific with our cronService schedule.


!!Note: The title of the failing test on line 37 should be: "a day after Feb 13 at 4:30AM, scrapeContestData to NOT BE called."

Now we have one test where we don't expect the service to run which is failing. We'll go ahead to make this green, and when we do we'll move on to the next test, and so on, and so forth.

A couple of moments later we pass our second test by saying run every second on sunday only


OOPS! We broke our first test


But that's odd, because Feb 13th IS a sunday. Well, turns out the way we're mocking our dates isn't taking effect. Okayyy, I noticed in the nest codebase they use sinon fake timers, so rather than mocking, I'm going to switch over to using sinon.

Ahhh, actually scratch that! That's quite a leap to conclude the mock isn't working, so let's take a closer look at the cron config.

@Cron('* * * * sun *')

From (
* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)

Looks like I put sun in the wrong place. So, correcting this I should get both tests passing but...

Nahh, that didn't work. So what's wrong?

I decide to log the date I have after I have mocked my and this log confirms that in the second test the mock isn't taking effect in the sense that it's not wholistic. does get mocked, but new Date.getDay() still returns sun instead of mon. Therefore perhaps switching to sinon is a good idea.

So let's do that. We'll add sinon as a dev dependency and use it just as they used it in the nest codebase.

Using sinon to get our tests passing again

A Couple of moments later I've added sinon and importantly sinon typescript types, and it's looking good now 🥳 🥳 🥳 🥳 🥳 🥳 .


The rest of session four is spent bringing in new test cases one at a time. When I'm done, all my weekly test cases pass with the cron specification below, giving me some confidence that my cron job will run at the time I intend it to run.

export class TasksService {
  private readonly logger = new Logger(;

  _called = 0;

  @Cron('* 30 4 * * sun')
  scrapeContestData(): void {
    this._called += 1;

Before ending this session I've also decided to push the code to github so that I don't lose all the work that went into this session 😅 .

In the next session, I'll focus on bringing the biweekly tests online, doing some refactoring to make these tests more maintainable from the point of view of wanting to change the scheduled run time. I.e we don't want to touch 16 test cases each time we change the schedule, so a loop will be helpful. And following this refactoring, I'll pull in the logic I implemented in part 1 of these articles for actually scrapping the leetcode contest data.