0

I recently converted my project to TypeScript and some Jest tests are not working anymore. It seems that the next function below is not called anymore with an AppError object but with an Error object.

The assertion expect(next).toHaveBeenCalledWith(expect.any(AppError)); used to pass but now I get this result

AuthController > login > error cases > should return error if user is inactive
-----
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: Any<AppError>
Received: [Error: Incorrect email or password, or user no longer active]

Number of calls: 1

It seems next is called with an object of type Error, not of type AppError anymore.

This is my test file:


let req, res, next;
beforeEach(() => {
    ...
    
    next = jest.fn().mockImplementation(function (err) {
        console.error(err);
    });
});
...
await authController.login(req, res, next);        
expect(next).toHaveBeenCalledWith(expect.any(AppError));

This is my login method.

import AppError from '../utils/appError';

const login = async (req, res, next) => {
    ...
    if (!user || !(await user.isCorrectPassword(password, user.password))) {
        return next(
          new AppError('Incorrect email or password, or user no longer active', 401)
        );
    }

And this is my App Error:

class AppError extends Error {
  public statusCode: string;
  public status: string;
  public isOperational: boolean;
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;
  }
}

export default AppError;

And just in case, before you asked, this is an extract of my package.json

"devDependencies": {
    "@types/jest": "^29.4.0",
    "jest": "^29.4.2",
    "ts-jest": "^29.0.5",
  },
nicolasdaudin
  • 302
  • 1
  • 4
  • 15
  • 1
    I think I ran into some quirky behavior extending Error before where the class that’s logged was still Error instead of mine. Did you by chance confirm that switching to `expect.any(Error)` made it pass, just to make sure we’re not dealing with a different quirky issue? – David R Feb 14 '23 at 08:36
  • Hi David, thanks for your help. I just switched (should have tried it earlier) and indeed, it passes correctly. So it's definitely something quirky extending Error (which, I repeat, did not happen before switching to TS). – nicolasdaudin Feb 14 '23 at 09:20
  • Btw I know there are workaround like `expect().toHaveBeenCalledWith(expect.objectContaining())` and matching the properties of my custom error object, but I want to understand why `expect.any(AppError)` is not working... – nicolasdaudin Feb 14 '23 at 09:22
  • 1
    That is interesting… there does seem to be a TypeScript specific issue according to [this other question answer](https://stackoverflow.com/a/41102306). I’d be curious if any of the solutions they or others suggest would help, as it seems like you would just need the prototype/target correctly set. – David R Feb 14 '23 at 09:43
  • Hi, indeed. My TS target was es5 for some reason. With es2015, no more problems. Although it's weird anyway that es5 had this special behavior for Error constructor that broke the prototype chain instead of keeping `this`. @DavidR feel free to add a proper answer so that I can upvote you. Thanks a million by the way! I was getting crazy!!! – nicolasdaudin Feb 14 '23 at 10:08
  • Oh weird ha ha. I agree it is odd behavior. But awesome that it worked and ended up being a simple fix! Glad I could help! Went ahead and added an answer with the info/solution we came to. – David R Feb 14 '23 at 16:01

1 Answers1

1

We resolved it in the comments, after I came across this answer around extending the Error class in Typescript. It turns out that the issue was having TS target es5 which has the known issue. While the answers there provide solutions if keeping es5, another way is to just change to a newer es version, which was the used solution here since es5 seems to not have been intentional anyway.

David R
  • 493
  • 2
  • 8