Adding up on the great answer by @Alex Chebotarsky.
After some unit testing I found that some additional checks are prudent:
/**
 * Generates a random int within the max and min range.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 */
export const randomInt = (
  min: number,
  max: number,
): number => (Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min)) + Math.ceil(min)));
/**
 * Generates a random int within the max and min range with an array of excludes.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 * @param excludes
 */
export const randomIntWithExclude = (
  min: number,
  max: number,
  excludes: number[] = [],
): number => {
  if (min === max && excludes.includes(min)) throw new RangeError('All values are excluded');
  if (min === max) return min;
  if (max < min) [max, min] = [min, max];
  let num = randomInt(min, max);
  if (!excludes || !excludes.length) return num;
  excludes
    .sort((a, b) => a - b)
    .every((except) => except <= num && (num >= max ? num -= 1 : num += 1, true));
  if (excludes.includes(num)) throw new RangeError('All values are excluded');
  return num;
};
If you are interested, here goes the unit test:
import {
  genRndNumUniqArray,
  randomIntWithExclude,
  randomInt,
} from './mathFuncs';
describe('[NumberFuncs]', () => {
  test.repeats(
    { times: 1000 },
    '[randomIntWithExclude] Should generate a random number excluding values in an array',
    () => {
      const excludesLength = randomInt(0, 10);
      const excludes = excludesLength
        ? genRndNumUniqArray(0, 100, excludesLength)
        : [];
      const [min, max] = excludes.length
        ? [Math.min(...excludes), Math.max(...excludes)]
        : [randomInt(0, 10), randomInt(10, 100)];
      try {
        const num = randomIntWithExclude(min, max, excludes);
        expect(num).not.toBeIncludedIn(excludes);
        expect(num).toBeGreaterThanOrEqual(min);
        expect(num).toBeLessThan(max);
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );
  test.repeats(
    { times: 100 },
    '[randomIntWithExclude] Should throw a `RangeError` if all possible values are in the excludes array',
    () => {
      const excludes = [...Array(randomInt(2, 10)).keys()];
      const [min, max] = [Math.min(...excludes), Math.max(...excludes)];
      try {
        randomIntWithExclude(min, max, excludes);
        expect(true).toBe(false); // This is not supposed to be reached since the code above throws an error
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );
});
This function is a dependency for the unit test:
/**
 * Generates an array of unique numbers
 * @param min
 * @param max
 * @param size
 */
export function genRndNumUniqArray(min: number, max: number, size: number): number[] {
  const rng = Math.min(max - min, size);
  if (rng < 1) return [];
  const nums = new Set<number>();
  while (nums.size !== rng) {
    const n = randomInt(min, max);
    nums.add(n);
  }
  return Array.from(nums);
}
And if you are even more interested about the test.repeats, it is a custom jest extension:
./jest.extends.ts
const handleError = ({
  name,
  errors,
  failPct,
  canFailPct,
  passIfOnePasses,
  debug,
  times,
  passes,
}: {
  name: string,
  times: number,
  canFailPct: number,
  passIfOnePasses?: boolean,
  passes: number[]
  errors: [number, any][],
  failPct: number,
  debug?: boolean,
}) => {
  if (passIfOnePasses && passes.length) return;
  if (errors.length && failPct > (canFailPct ?? 0)) {
    if (debug) {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Errors:
${errors.map((e) => `RUN: ${e[0]}\n${e[1].message}`).join('\n\n')}
`);
    } else {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Last error:
${errors[errors.length - 1][1]}\n
You can pass the \x1b[1;33m\`debug: true\`\x1b[0m option to see all errors.
`);
    }
  }
};
const repeatTest = async (
  options: jest.RepeatWithCanFail | jest.RepeatWithPass | jest.RepeatWithDefaults,
  name: string,
  fn?: jest.ProvidesCallback,
  timeout?: number,
) => {
  if (options.canFailPct && (options.canFailPct < 0 || options.canFailPct > 1)) {
    throw new Error('`canFailPct` must be between 0 and 1');
  }
  const passes: number[] = [];
  const errors: [number, any][] = [];
  return test(name, async () => {
    for await (const i of [...Array(options.times).keys()]) {
      try {
        if (fn) {
          // @ts-ignore
          await fn();
          passes.push(i);
        }
      } catch (error) {
        errors.push([i, error.stack ?? error.toString()]);
      }
    }
    const failPct = errors.length / options.times;
    handleError({
      name,
      errors,
      failPct,
      canFailPct: options.canFailPct ?? 0,
      passIfOnePasses: options.passIfOnePasses,
      debug: options.debug,
      times: options.times,
      passes,
    });
  }, timeout);
};
test.repeats = repeatTest;
it.repeats = repeatTest;
It prints this on failing tests:
  [NumberFuncs]
    ✕ [getRandomIntWithExclude] (216 ms)
  ● [NumberFuncs] › [randomIntWithExclude]
    Test: [randomIntWithExclude]
    Ran: 1000 times
    Failures: 95
    Passes: 905
    Fail rate: 9.5%
    Last error:
    Error: expect(received).toBeGreaterThanOrEqual(expected)
    Expected: >= 67
    Received:    66
./jest.config.js
Make sure to run the extension file before tests and include the jest custom types in jest.d.ts and tsconfig.json if using typescript.
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  ...
  setupFilesAfterEnv: ['./jest/extends.ts'],
  ...
};
jest.d.ts
export {}
declare global {
  namespace jest {  
    type RepeatWithCanFail = {
      times: number,
      canFailPct: number,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }
    
    type RepeatWithPass = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses: boolean,
      debug?: boolean,
    }
    
    type RepeatWithDefaults = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }
    type RepeatOpts<O = any> =
    O extends RepeatWithCanFail
    ? RepeatWithCanFail
    : O extends RepeatWithPass
    ? RepeatWithPass
    : RepeatWithDefaults;
    interface It {
      repeats: <O extends RepeatOpts>(
        options: RepeatOpts<O>,
        name: string,
        fn?: jest.ProvidesCallback,
        timeout?: number,
      ) => void;
    }
  }
}