diff --git a/docs/curriculum-helpers.md b/docs/curriculum-helpers.md index 7f5ccfb..555a82e 100644 --- a/docs/curriculum-helpers.md +++ b/docs/curriculum-helpers.md @@ -1,5 +1,20 @@ # Curriculum Helpers +## RandomMocker + +Mocks Math.random for testing purposes. Each time `mock()` is called the pseudo-random number generator is reset to its initial state, so that the same sequence of random numbers is generated each time. `restore()` restores the native Math.random function. + +```javascript +const randomMocker = new RandomMocker(); +randomMocker.mock(); +Math.random(); // first call is always 0.2523451747838408 +Math.random(); // second call is always 0.08812504541128874 +randomMocker.mock(); +Math.random(); // generator is reset, so we get 0.2523451747838408 again +randomMocker.restore(); +Math.random(); // back to native Math.random +``` + ## concatRegex Combines one or more regular expressions into one. diff --git a/lib/__tests__/curriculum-helper.test.ts b/lib/__tests__/curriculum-helper.test.ts index 34b08d9..fec7014 100644 --- a/lib/__tests__/curriculum-helper.test.ts +++ b/lib/__tests__/curriculum-helper.test.ts @@ -22,6 +22,51 @@ const { jsCodeWithCommentedCall, } = jsTestValues; +describe("RandomMocker", () => { + let random: () => number; + + beforeEach(() => { + random = Math.random; + }); + + afterEach(() => { + Math.random = random; + }); + + describe("mock", () => { + it('should replace "Math.random" with a mock function', () => { + const mocker = new helper.RandomMocker(); + mocker.mock(); + expect(Math.random).not.toBe(random); + }); + + it('should mock "Math.random" with a pseudorandom function', () => { + const mocker = new helper.RandomMocker(); + mocker.mock(); + // Predictable random values: + expect(Math.random()).toBe(0.2523451747838408); + expect(Math.random()).toBe(0.08812504541128874); + }); + + it("should reset the pseudorandom function when called multiple times", () => { + const mocker = new helper.RandomMocker(); + mocker.mock(); + expect(Math.random()).toBe(0.2523451747838408); + mocker.mock(); + expect(Math.random()).toBe(0.2523451747838408); + }); + }); + + describe("restore", () => { + it('should restore "Math.random" to its original function', () => { + const mocker = new helper.RandomMocker(); + mocker.mock(); + mocker.restore(); + expect(Math.random).toBe(random); + }); + }); +}); + describe("removeWhiteSpace", () => { const { removeWhiteSpace } = helper; it("returns a string", () => { diff --git a/lib/index.ts b/lib/index.ts index 4cfd8d9..e1a47ff 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,37 @@ import { strip } from "./strip"; import astHelpers from "../python/py_helpers.py"; +/** + * The `RandomMocker` class provides functionality to mock and restore the global `Math.random` function. + * It replaces the default random number generator with a deterministic pseudo-random number generator. + */ +export class RandomMocker { + private random: () => number; + + constructor() { + this.random = Math.random; + } + + private createRandom() { + let seed = 42; + const a = 1664525; + const c = 1013904223; + const mod = 2 ** 32; + return () => { + seed = (a * seed + c) % mod; + return seed / mod; + }; + } + + mock(): void { + globalThis.Math.random = this.createRandom(); + } + + restore(): void { + globalThis.Math.random = this.random; + } +} + /** * Removes every HTML-comment from the string that is provided * @param {String} str a HTML-string where the comments need to be removed of