1

I am working on a react webapp and have run into a really strange issue when trying to get my unit tests to pass.

Essentially I have a javascript class called Time that's essentially just the time component of a DateTime (its based on a Luxon Duration). I have added isBefore(Time) and isAfter(Time) methods to this class to allow two times to be compared.

These methods are used in a helper function (checkTimeRange) in another file to implement a three-way comparison (i.e. it can tell if one Time is before, during, or after a range denoted by two other Times).

Here's the state of my unit tests:

  • Time tests: passing, including for isAfter.
  • helper tests: passing, including for checkTimeRange
  • School, ClassPeriod, and BellSchedule (all classes that contain methods that indirectly depend on checkTimeRange) tests for those methods are failing because <variable storing a Time>.isAfter is not a function

In researching, I found this, which seemed to describe the issue perfectly (in the title at least), but the solution seemed to be a simple typo, which doesn't appear to be the case here.

Things I have tried:

  • checking out the project in another folder
  • deleting and re-installing the node_modules
  • clearing the yarn cache
  • updating dependencies
  • changing the functions from public function name () {} syntax to arrow function (public const name = () => {}) syntax
  • checking for typos and variable shadowing
  • running tests both via yarn test and using the VSCode integration thats showing up in my editor (idk whats causing this to show up though)
  • placing a unit test for Time.isAfter right alongside the case that is failing because isAfter is not a function to show that its not a difference in how the different unit test files are set up
  • updating yarn from 1.17.3 to 1.22.19

I still have basically no idea as to what may be causing this. I have a feeling that maybe something deep inside Jest is not working properly, but I don't really know whats causing this contradicting behavior.

Here is the branch of the repo where im seeing this behavior if anyone needs more info on whats happening

The test that i'm looking at is the last one in the file of unit tests for the School class. However many of the other classes, like ClassPeriod also have their last test failing for similar reasons.

MoralCode
  • 1,954
  • 1
  • 20
  • 42
  • 1
    Could you show us a minimal example? Or point out where the problem is in your repo and where your Time class and its `isAfter` methods are? – Schwern Jul 22 '22 at 21:55
  • all the classes i referred to and their unit tests are in the `src/@types` folder. the helpers file i referenced is at `src/utils/helpers.tsx`. tests can be run with `yarn yest`. let me know if you need more info! happy to create a chat (on SO or otherwise) and help walk through what im seeing – MoralCode Jul 22 '22 at 22:26
  • I also think i might have substantially narrowed down the problem. Commenting out the if statement at the start of the `checkTimeRange` helper (the one that swaps `startTime` and `endTime`) seems to eliminate the problem with the tests (by causing the tests to report failure based on the actual test results). I have no idea why these lines are causing this problem though – MoralCode Jul 22 '22 at 22:32
  • narrowing it down further: the problem also goes away if the first parameter `checkTime` is used instead of the second (`startTime`) in the `if (checkTime.isAfter(endTime)) {` line of `checkTimeRange`. i have no idea why this may be – MoralCode Jul 23 '22 at 00:07
  • more weirdness: it seems like the order of the parameters matters. swapping the first two parameters in the function signature causes the error to change from `startTime.isAfter is not a function` to `checkTime.isBefore is not a function`. this line is further down the program. It seems like calling instance methods on the first parameter passed to a function is making it angry somehow – MoralCode Jul 23 '22 at 00:10
  • You'll have a better chance of getting an error if you provide specifics. And while it's good one can download your project and run your tests, a lot of people won't. Start with specifically which line of the test is failing and the output of the failing test. You're testing School.isInSession which is calling checkTimeRange? – Schwern Jul 23 '22 at 00:41

1 Answers1

1

tl;dr: <variable storing a Time> is not storing a Time.

Here is the test error.

  ● School › can check if school is in session

    TypeError: startTime.isAfter is not a function

      112 |
      113 |     // swap the values if startTime is after end time
    > 114 |     if (startTime.isAfter(endTime)) {
          |                   ^
      115 |         let t = startTime
      116 |         startTime = endTime
      117 |         endTime = t

      at checkTimeRange (src/utils/helpers.tsx:114:19)
      at School.isInSession (src/@types/school.ts:146:13)
      at Object.<anonymous> (src/@types/school.test.ts:97:23)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:404:19)

I added console.log(startTime);console.log(endTime); to checkTimeRange.

startTime and endTime are both Luxon DateTimes which do not have an isAfter function. startTime.isAfter(endTime) won't work.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Thanks so much for taking the time to clone this and post an answer! i saw this too when adding console.log's as well. This confuses me as the parameters are typed as `Time` so I'm not sure why this function call is the first thing complaining and not TypeScript. also ive noticed the error go away if the whole if block starting on that line 114 gets commented out. Like this seems to only be an issue with the first parameter given some of the testing from the comments i left on the question – MoralCode Jul 23 '22 at 01:22
  • I wondered about that as well. I don't know Typescript, but it's it possible that,1) Datetime is a subtype of Time and 2) that the Time there is referring to a different Time? It's possible there is another Time loaded by something else shadowing yours. Try changing the name of your Time type. – Schwern Jul 23 '22 at 04:39
  • turns out some of my constants that I defined for the unit tests were still setting things up with DateTime in place of Time's. there was an error there that was watrning me, but somehow that didn't get picked up at the point where it actually tried to call isAfter. still not sure why TypeScript didnt freak out every time that DateTime went through a parameter or return value that was typed as Time since Time is a standalone class (doesnt extend or implement anything) and uses a Luxon Duration (not even a DateTime) internally – MoralCode Jul 23 '22 at 15:14
  • @MoralCode Is there a way to examine the Time class inside checkTimeRange to determine if it's your Time class or some other Time class? – Schwern Jul 23 '22 at 20:29
  • yeah, I've looked at the list of imports at the top of the page and dont see anything conflicting with Time. the only import for that name is the one that points to `./time` which is the `time.ts` file i wrote. i dont think TypeScript or react offer any default globals with the name of Time – MoralCode Jul 24 '22 at 21:36