Getting our Cron Tests Passing in NestJs
Session IV of Building a Leetcode Contest Percentile Analyser Part II
Search for a command to run...
Session IV of Building a Leetcode Contest Percentile Analyser Part II
No comments yet. Be the first to comment.
This series documents the steps taken to build an oss Leetcode percentile analyser.
Continuation of Session IV of Building a Leetcode Percentile Analyser II
Short answer is: You canβt But there are other options! These options help you: Expand the β¦ N more β¦ values in your intellisense pop-up window Expand Type contracts to view all properties in a combined type without using the subtype names. 1 - U...
Introduction eBPF (extended Berkeley Packet Filter) is a successor to the BPF (Berkeley Packet Filter) which already existed as part of the Linux kernel as far back as 1992. It allows developers to safely extend the kernel based on sys-call events, a...
I'm about to host my own ATProto server. It's 01:09am my time, but I want to do this now, before bed, so that it doesn't distract me from important work tomorrow. If you don't know what this is visit: I have my first issue. The issue is that my doma...
I'm about to do my first ever domain transfer. It's from Squarespace to Cloudflare, and I hope it goes smoothly. I need to do this because Squarespace is woefully under-featured for the needs of building an indie sass product. I have a major concer...
And why you should be very intentionally optimizing your workflows with AI tools - they really help!
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 date.now for my first test.
import { Test, TestingModule } from '@nestjs/testing';
import { TasksService } from './tasks.service';
const TasksServiceMock = {
scrapeContestData: jest.fn().mockImplementation(() => {
return;
}),
};
describe('TasksService', () => {
let tasksService: TasksService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
{
provide: TasksService,
useValue: TasksServiceMock,
},
],
}).compile();
tasksService = moduleRef.get<TasksService>(TasksService);
});
describe('weekly', () => {
test('Feb 13 at 4:30AM, scrapeContestData TO BE called', () => {
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() =>
new Date('2022-02-13T11:01:58.135Z').valueOf()
);
expect(tasksService.scrapeContestData).toHaveBeenCalled();
});
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 date.now, 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.
test('Feb 13 at 4:30AM, scrapeContestData TO BE called', () => {
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() =>
new Date('2022-02-13T04:30Z').valueOf()
);
expect(tasksService.scrapeContestData).toHaveBeenCalled();
});
At this point the test looks fine... now we need to make it pass.
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.
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**/ clock.tick(3000); expect(service.callsCount).toEqual(3); });@Injectable() export class CronService { callsCount = 0; /**They keep track of cron count here**/ dynamicCallsCount = 0; ... }
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


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 date.now, 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 (https://docs.nestjs.com/techniques/task-scheduling)
* * * * * *
| | | | | |
| | | | | 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 Date.now and this log confirms that in the second test the date.now mock isn't taking effect in the sense that it's not wholistic. Date.now 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.
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.
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
_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.