How to Test Promise Recursive Processing in JEST

Asked 2 years ago, Updated 2 years ago, 181 views

I don't know how to test Promise recursive processing in JEST.

I am writing a test using JEST.
In this test, the retry function that recurs until Promise is resolved is tested.

export function retry<T>(fn:()=>Promise<T>, limit:number=5, interval:number=10)—Promise<T>{
  return new Promise ((resolve, reject) = > {
    fn()
      .then(resolve)
      .catch(error)=>{
        setTimeout(async()=>{
          // Reject when the maximum number of retries is exceeded
          if(limit===1){
            reject(error);
            return;
          }
          // Recursive callback processing if the maximum number of retries is less than the maximum number of retries.
          wait retry (fn, limit-1, interval);
        }, interval);
      });
  });
}

Perform the following tests on the retry function above:
①Make sure to pass the Promise to resolve, and the retry function is resolved on the first run.
②Pass the Promise to resolve on the third run, and the retry function will be resolved on the third run

I thought that writing these in JEST would look like the following.

describe('retry',()=>{
  test('resolve on the first call', async()=>{
    const fn = jest.fn().mockResolvedValue('resolve!');
    wait retry(fn);
    expect(fn.mock.calls.length).toBe(1);
  });

  test('resolve on the third call', async()=>{
    const fn = jest.fn()
               .mockRejectedValueOnce(newError('Async error'))
               .mockRejectedValueOnce(newError('Async error'))
               .mockResolvedValue('OK');
    expect(fn.mock.calls.length).toBe(3)
  });
});

As a result, we failed with the following error:

Timeout-Async callback was not invoked with the 5000ms timeout specified by jest.setTimeout.Error: 
    > 40 | test('resolve on the third call', async() = > {
         |   ^
      41 | const fn = jest
      42|.fn()
      43 | .mockRejectedValueOnce(newError('Async error'))

For this error, I think the JEST setting will do something about it.However, fundamentally speaking, I do not know if this is the way to test Promise recursive processing in JEST.

typescript test promise jestjs

2022-09-30 18:05

1 Answers

I don't know if you are still watching this question because it was more than a year ago, but I will answer it.

First, the code for the question is not handled correctly.

setTimeout(async()=>{
  // Reject when the maximum number of retries is exceeded
  if(limit===1){
    reject(error);
    return;
  }
  // Recursive callback processing if the maximum number of retries is less than the maximum number of retries.
  wait retry (fn, limit-1, interval);
}, interval);

As for this implementation, when wait retry(...) throws the error, the error has not been passed to reject, so you will die after giving an error during the test run.

Timeout-Async callback was not invoked with the 5000ms timeout specified by jest.setTimeout.Error: 

This is also the reason for the log output.Therefore, the first step to correct the implementation is as follows:

export function retry<T>(fn:()=>Promise<T>, limit:number=5, interval:number=10)—Promise<T>{
  return new Promise ((resolve, reject) = > {
    fn()
      .then(resolve)
      .catch(error)=>{
        setTimeout(()=>{
          // Reject when the maximum number of retries is exceeded
          if(limit===1){
            reject(error);
            return;
          }
          // Recursive callback processing if the maximum number of retries is less than the maximum number of retries.
          retry(fn, limit-1, interval).reject(resolve);
        }, interval);
      });
  });
}

Using async/await to write a little bit more clearly would look like this:

export const wait=async(ms:number)=>
  new Promise ((resolve)=>setTimeout(resolve,ms));

export async function retry <T>(
  fn:() = > Promise <T>,
  limit : number = 5,
  interval : number = 10
)—Promise <T>{
  try{
    return wait fn();
  } catch(error){
    if(limit===1){
      through new Error (error);
    }
    wait wait (interval);
    return retry (fn, limit-1, interval);
  }
}

If you write the rewritten test code:

describe("retry",()=>{
  test("resolve on the first call", async()=>{
    const fn = jest.fn().mockResolvedValue("resolve!");
    wait retry(fn);
    expect(fn.mock.calls.length).toBe(1);
  });

  test("retry reject test", async()=>{
    let counter = 6;
    const callback=async()=>{
      counter-=1;
      if(counter>0){
        US>throw new Error (`${counter} times left`); // Show the number of times as of the time of the row
      } else{
        return "Success Retry";
      }
    };
    const result=retry(callback);
    wait expect(result).rejects.toThrow("One more time");
  });

  test("retry resolve test", async()=>{
    let counter = 5;
    const callback=async()=>{
      counter-=1;
      if(counter>0){
        US>throw new Error (`${counter} remaining');
      } else{
        return "Success Retry";
      }
    };
    const result=retry(callback);
    wait expect(result).resolve.toBe("Success Retry");
  });
});

This code has 100% coverage.

Configuring jest.config.js

module.exports={
  "automock": false,
  "unmockedModulePathPatterns": ["<rootDir>/node_modules/*",
  "roots": ["<rootDir>/src"],
  "globals": {
    "ts-jest": {
      "tsConfig": "tsconfig.json",
      "diagnostics"—false
    }
  },
  "moduleDirectories": "node_modules",
  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "node",
  "testMatch": ["**/__test__/*.test.+(ts|tsx)"],
  "collectCoverage": true,
  "transform": {
    "^.+\\.(ts|tsx)$"—"ts-jest"
  }
};

This test is short but includes the following:

  • Asynchronous processing
  • setTimeout (timer system)
  • Retry Processing (Resend Processing)

If you are writing a test for asynchronous processing, you must ensure that the code you are testing is wrapped in try-catch or resolve/reject.In order to do this, make sure that there is no omission, such as splitting the code, looking at the test coverage, or identifying where it has not been tested.It may be supported by TypeScript type checking and link tools.

Time Mocks is available in jest for timer tests, so let's use it.However, in this case, the time was shorter than the jest timeout (10ms), so I did not specify it.
https://deltice.github.io/jest/docs/ja/timer-mocks.html

As for the retry processing, there is a "number of times" part, so if the processing branches, it would be better to have a state on the test code side so that the branch can be changed from the outside.This time it happened to be a process for asynchronous functions, so I had to receive the value to be verified in the form wait expect(result).resolves/wait expect(result).rejects.

That was the strategy for this test code.

I hope it will be helpful for those who are watching this.


2022-09-30 18:05

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.