#jest test function containing a promise chain with nested .then

1 messages · Page 1 of 1 (latest)

hearty wave
#

Hi,

So I am trying to understand the limits of jest and I know that if I have a function that calls a function which returns a promise I'm able to test it.
If that promise has multiple .then' s chained and one of them calls a different function that has it own .then, i have no idea how I would test the .then return value.
To give an example I have the following express middleware function:

export const canAccess =
  (module: string, action: string) =>
  (req: Request, res: Response, next: NextFunction) => {
    try {
      var cookie = JSON.parse(req.cookies["something"]);
      return AuthController.validateToken(cookie._id, process.env.TOKEN_SECRET)
        .then((decoded: userInviteJwtToken) => {
          if (decoded.companyId !== cookie.companyId) {
            throw new Error("Cookie and token contents are different!");
          }
          return decoded;
        })
        .then((decoded: userInviteJwtToken) =>
          CompanyController.getCompany(decoded.companyId).then((company) => { return {decodedToken: decoded, company: company};})
        )
        .then((payload) =>
          RoleController.getRole(payload.decodedToken.roleId).then((role) => { return {...payload, role};})
        )
        .then((payload) => {
          if (payload) {
            // do something
            return
          }
          throw new Error(
            "You don't have the right permissions to access this resource!"
          );
        })
        .catch((err) => {
          if (err.status) {
            res.status(err.status).json({ message: err.message });
          } else {
            res.status(400).json({ message: err.message });
          }
        });
    } catch (err) {
      res.status(400).json({
        message: err,
      });
    }
  };

Now I want to know how I can test this function, more specifically how I can test that the .then after the .getCompany method is returning both token and company data.

Any ideas?

Thx!

#

jest test function containing a promise chain with nested .then

covert cradle
#

My idea would be to refactor this to use async/await to begin with, that would help a lot in understanding what's going on.

#

Breaking this out into smaller pieces would also help a lot I think.

hearty wave
#

I guess that's true, but then the question would be pointless...
For me this is clean and it makes more sense then using async await (never really liked it) as this is divided into different blocks of things you are doing
but i guess that's just a personal opinion

errant bobcat
#

Even if you leave it as .thens, you're going to need to separate each step into its own function if you want to be able to test them all individually

hearty wave
#

okay so testing this is not possible then?

export const canAccess =
  (module: string, action: string) =>
  (req: Request, res: Response, next: NextFunction) => {
  return AuthController.validateToken(token)
    .then((token)=> 
      CompanyController.getCompany(token.companyId)
        .then((company) => {
          /*
           * testing the return here doesn't work then?
           */
          return { token: token, company: company }
        });
    )
}
covert cradle
# hearty wave I guess that's true, but then the question would be pointless... For me this is ...

Well I wanted to makes sense of it, so if you compare it....

export const canAccess =
  (module: string, action: string) =>
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const cookie = JSON.parse(req.cookies['something'])

      const decodedToken: userInviteJwtToken =
        await AuthController.validateToken(cookie._id, process.env.TOKEN_SECRET)

      if (decodedToken.companyId !== cookie.companyId) {
        throw new Error('Cookie and token contents are different!')
      }

      const [company, role] = await Promise.all([
        CompanyController.getCompany(decodedToken.companyId),
        RoleController.getRole(decodedToken.roleId),
      ])

      const payload = { decodedToken, company, role }
      // do something with the payload
    } catch (err) {
      if (err.status) {
        res.status(err.status).json({ message: err.message })
      } else {
        res.status(400).json({ message: err.message })
      }
    }
  }

I personally definitely prefer this ^

#

My brain hurts thinking about the .then version, but here you can clearly see you just need to mock 3 methods.

#

If you insist on using .then callbacks then you should definitely have them as separate testable functions (units... since you're doing unit testing)

hearty wave
#

in that case can something like this be easily tested then:

export const canAccess =
  (module: string, action: string) =>
  (req: Request, res: Response, next: NextFunction) => {
    try {
      var cookie = JSON.parse(req.cookies["something"]);
      return AuthController.validateToken(cookie._id, process.env.TOKEN_SECRET)
        .then(getCompany)
        .then(getRole)
        .then(determineAccess);
    } catch (err) {
      if (err.status) {
        res.status(err.status).json({ message: err.message });
      } else {
        res.status(400).json({ message: err.message });
      }
    }
  };
#

like I want to test that it actually calls those functions

covert cradle
#

Yeah, roughly, it should look sth like that.

#

Don't forget about the .catch though.

hearty wave
#

i already have a catch around it though?

covert cradle
#

That won't work...

hearty wave
#

oh

#

okay fair enough

covert cradle
#

You either use async/await and try/catch or you use .then and .catch

#

You should wrap the JSON.parse call in a try/catch though.

hearty wave
#

ye definitely

covert cradle
#

Also a minor thing but it would be faster to race the getCompany and getRole calls.

hearty wave
#

well i guess i could do that aswell:

export const canAccess =
  (module: string, action: string) =>
  (req: Request, res: Response, next: NextFunction) => {
    try {
      var cookie = JSON.parse(req.cookies["something"]);
    } catch (err) {
      res.status(400).json({ message: err });
    }
    return AuthController.validateToken(cookie._id, process.env.TOKEN_SECRET)
      .then(getCompanyAndRole)
      .then(determineAccess)
      .then((canAccess: boolean)=>{
        if(canAccess) {
          next();
        } else {
          res.status(401).json({message: "Not Authorized"});
        }
      })
      .catch((err)=> {
        if (err.status) {
          res.status(err.status).json({ message: err.message });
        } else {
          res.status(400).json({ message: err.message });
        }
      });
  };
#

okay so back to my testing question how can i now test this and ensure it calls getCompanyAndRole and determineAccess?

#

do i just spy on on the function and say expect(function).toHaveBeenCalledTimes(1)?

covert cradle
#

Well you don't wanna run the side effects I assume, so you should stub out the entire thing.

hearty wave
#

could you clarify that a bit more, cause im kinda new to it

covert cradle
#

I mean I'm guessing getCompanyAndRole does something with a db. You should have a mock return some data you'd expect in a promise.

hearty wave
#

oh oh ye

covert cradle
#

Also in a rejection, to test both branches.

hearty wave
#

i'd do something like this right:

var spyGetCompany = jest
  .spyOn(this, "getCompany")
  .mockResolvedValue(payload);
#

i don't really know how to select a local function using spyon so i just placed this, dunno if its right or not

covert cradle
#

I'd have to look it up..

covert cradle
hearty wave
#

actually nvm already now the answer if I export the file and give it a name I could use that name instead...

#

oh well lucky guess then xD

#

just learned how to use spy on yesterday so :p

covert cradle
#

🕵️

hearty wave
#

😁

#

Thx for the help guys, think ill be able to do it now!

#

(and if not ill just open a new one :p)

covert cradle
#

Cool good luck!

hearty wave
#

thx!