Repository: dubzzz/fast-check
Pull request #1744 (2021-04-14 20:09:16)

🚚 Move memo arbitrary into /arbitrary
<!-- Why is this PR for? -->
<!-- Describe the reason why you opened this PR or give a link towards the associated issue. -->

## In a nutshell

❌ New feature
❌ Fix an issue
❌ Documentation improvement
✔️ Other: *moving code*

(✔️: yes, ❌: no)

## Potential impacts

None



Patch:
diff --git a/src/check/arbitrary/MemoArbitrary.ts b/src/arbitrary/memo.ts
similarity index 94%
rename from src/check/arbitrary/MemoArbitrary.ts
rename to src/arbitrary/memo.ts
index 547694d4..33c03625 100644
--- a/src/check/arbitrary/MemoArbitrary.ts
+++ b/src/arbitrary/memo.ts
@@ -1,4 +1,4 @@
-import { Arbitrary } from './definition/Arbitrary';
+import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
 
 /**
  * Output type for {@link memo}
diff --git a/src/check/arbitrary/ObjectArbitrary.ts b/src/check/arbitrary/ObjectArbitrary.ts
index 11d4b359..1bd2b1f4 100644
--- a/src/check/arbitrary/ObjectArbitrary.ts
+++ b/src/check/arbitrary/ObjectArbitrary.ts
@@ -8,7 +8,7 @@ import { dictionary, toObject } from './DictionaryArbitrary';
 import { double } from './FloatingPointArbitrary';
 import { frequency } from '../../arbitrary/frequency';
 import { maxSafeInteger } from '../../arbitrary/maxSafeInteger';
-import { memo, Memo } from './MemoArbitrary';
+import { memo, Memo } from '../../arbitrary/memo';
 import { oneof } from '../../arbitrary/oneof';
 import { set } from '../../arbitrary/set';
 import { string, unicodeString } from './StringArbitrary';
diff --git a/src/fast-check-default.ts b/src/fast-check-default.ts
index c5f2226a..d705f23c 100644
--- a/src/fast-check-default.ts
+++ b/src/fast-check-default.ts
@@ -48,7 +48,7 @@ import { ipV4, ipV4Extended, ipV6 } from './check/arbitrary/IpArbitrary';
 import { letrec } from './arbitrary/letrec';
 import { lorem, LoremConstraints } from './check/arbitrary/LoremArbitrary';
 import { mapToConstant } from './check/arbitrary/MapToConstantArbitrary';
-import { memo, Memo } from './check/arbitrary/MemoArbitrary';
+import { memo, Memo } from './arbitrary/memo';
 import { mixedCase, MixedCaseConstraints } from './check/arbitrary/MixedCaseArbitrary';
 import {
   anything,


Test patch:
diff --git a/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
new file mode 100644
index 00000000..75a2817a
--- /dev/null
+++ b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
@@ -0,0 +1,98 @@
+import { LazyArbitrary } from '../../../../src/arbitrary/_internals/LazyArbitrary';
+import { NextValue, Stream } from '../../../../src/fast-check-default';
+import { fakeNextArbitrary } from '../../check/arbitrary/generic/NextArbitraryHelpers';
+import { fakeRandom } from '../../check/arbitrary/generic/RandomHelpers';
+
+describe('LazyArbitrary', () => {
+  describe('generate', () => {
+    it('should call underlying on generate', () => {
+      // Arrange
+      const value = new NextValue(Symbol(), Symbol());
+      const { instance: mrng } = fakeRandom();
+      const { instance: underlying, generate } = fakeNextArbitrary();
+      generate.mockReturnValue(value);
+      const lazy = new LazyArbitrary('id007');
+      lazy.underlying = underlying;
+
+      // Act
+      const out = lazy.generate(mrng, 2);
+
+      // Assert
+      expect(out).toBe(value);
+      expect(generate).toHaveBeenCalledWith(mrng, 2);
+    });
+
+    it('should fail to generate when no underlying arbitrary', () => {
+      // Arrange / Act
+      const { instance: mrng } = fakeRandom();
+      const lazy = new LazyArbitrary('id007');
+
+      // Assert
+      expect(() => lazy.generate(mrng, 2)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"id007\\" not correctly initialized"`
+      );
+    });
+  });
+
+  describe('canGenerate', () => {
+    it.each`
+      returnValue
+      ${false}
+      ${true}
+    `('should call underlying on canGenerate ($returnValue)', ({ returnValue }) => {
+      // Arrange
+      const value = Symbol();
+      const { instance: underlying, canGenerate } = fakeNextArbitrary();
+      canGenerate.mockReturnValue(returnValue);
+      const lazy = new LazyArbitrary('id007');
+      lazy.underlying = underlying;
+
+      // Act
+      const out = lazy.canGenerate(value);
+
+      // Assert
+      expect(out).toBe(returnValue);
+      expect(canGenerate).toHaveBeenCalledWith(value);
+    });
+
+    it('should fail to check canGenerate when no underlying arbitrary', () => {
+      // Arrange / Act
+      const lazy = new LazyArbitrary('id007');
+
+      // Assert
+      expect(() => lazy.canGenerate(1)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"id007\\" not correctly initialized"`
+      );
+    });
+  });
+
+  describe('shrink', () => {
+    it('should call underlying on shrink', () => {
+      // Arrange
+      const value = Symbol();
+      const context = Symbol();
+      const streamOutput = Stream.of(new NextValue(1));
+      const { instance: underlying, shrink } = fakeNextArbitrary();
+      shrink.mockReturnValue(streamOutput);
+      const lazy = new LazyArbitrary('id007');
+      lazy.underlying = underlying;
+
+      // Act
+      const out = lazy.shrink(value, context);
+
+      // Assert
+      expect(out).toBe(streamOutput);
+      expect(shrink).toHaveBeenCalledWith(value, context);
+    });
+
+    it('should fail to shrink when no underlying arbitrary', () => {
+      // Arrange / Act
+      const lazy = new LazyArbitrary('id007');
+
+      // Assert
+      expect(() => lazy.shrink(1, 2)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"id007\\" not correctly initialized"`
+      );
+    });
+  });
+});
diff --git a/test/unit/arbitrary/letrec.spec.ts b/test/unit/arbitrary/letrec.spec.ts
new file mode 100644
index 00000000..1f8052d1
--- /dev/null
+++ b/test/unit/arbitrary/letrec.spec.ts
@@ -0,0 +1,381 @@
+import { letrec as letrecOld } from '../../../src/arbitrary/letrec';
+import { LazyArbitrary } from '../../../src/arbitrary/_internals/LazyArbitrary';
+import { NextArbitrary } from '../../../src/check/arbitrary/definition/NextArbitrary';
+import { NextValue } from '../../../src/check/arbitrary/definition/NextValue';
+import { Arbitrary } from '../../../src/check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { Stream } from '../../../src/stream/Stream';
+import { FakeIntegerArbitrary, fakeNextArbitrary } from '../check/arbitrary/generic/NextArbitraryHelpers';
+import { fakeRandom } from '../check/arbitrary/generic/RandomHelpers';
+import {
+  assertGenerateEquivalentTo,
+  assertGenerateProducesSameValueGivenSameSeed,
+  assertGenerateProducesValuesFlaggedAsCanGenerate,
+  assertShrinkProducesSameValueWithoutInitialContext,
+  assertShrinkProducesValuesFlaggedAsCanGenerate,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+
+// Temporary rewrapping around letrec
+// should be removed with next major (no more Arbitrary, only NextArbitrary)
+export function letrec<T>(
+  builder: (tie: (key: string) => NextArbitrary<unknown>) => { [K in keyof T]: NextArbitrary<T[K]> }
+): { [K in keyof T]: NextArbitrary<T[K]> } {
+  const outOld = letrecOld((tieOld) => {
+    const tie: (key: string) => NextArbitrary<unknown> = (key) => convertToNext(tieOld(key));
+    const built = builder(tie);
+    const revampedBuilt: { [K in keyof T]: Arbitrary<T[K]> } = Object.create(null);
+    for (const k of Object.keys(built)) {
+      revampedBuilt[k as keyof T] = convertFromNext(built[k as keyof T]);
+    }
+    return revampedBuilt;
+  });
+  const out: { [K in keyof T]: NextArbitrary<T[K]> } = Object.create(null);
+  for (const k of Object.keys(outOld)) {
+    out[k as keyof T] = convertToNext(outOld[k as keyof T]);
+  }
+  return out;
+}
+
+describe('letrec', () => {
+  describe('builder', () => {
+    it('should be able to construct independant arbitraries', () => {
+      // Arrange
+      const { instance: expectedArb1 } = fakeNextArbitrary();
+      const { instance: expectedArb2 } = fakeNextArbitrary();
+
+      // Act
+      const { arb1, arb2 } = letrec((_tie) => ({
+        arb1: expectedArb1,
+        arb2: expectedArb2,
+      }));
+
+      // Assert
+      expect(arb1).toBe(expectedArb1);
+      expect(arb2).toBe(expectedArb2);
+    });
+
+    it('should not produce LazyArbitrary for no-tie constructs', () => {
+      // Arrange
+      const { instance: expectedArb } = fakeNextArbitrary();
+
+      // Act
+      const { arb } = letrec((_tie) => ({
+        arb: expectedArb,
+      }));
+
+      // Assert
+      expect(arb).not.toBeInstanceOf(LazyArbitrary);
+      expect(arb).toBe(expectedArb);
+    });
+
+    it('should not produce LazyArbitrary for indirect tie constructs', () => {
+      // Arrange / Act
+      const { arb } = letrec((tie) => {
+        const { instance: expectedArb, generate } = fakeNextArbitrary();
+        generate.mockImplementation((...args) => tie('arb').generate(...args));
+        return {
+          // arb is an arbitrary wrapping the tie value (as fc.array)
+          arb: expectedArb,
+        };
+      });
+
+      // Assert
+      expect(arb).not.toBeInstanceOf(LazyArbitrary);
+    });
+
+    it('should produce LazyArbitrary for direct tie constructs', () => {
+      // Arrange / Act
+      const { arb } = letrec((tie) => ({
+        arb: tie('arb'),
+      }));
+
+      // Assert
+      expect(arb).toBeInstanceOf(LazyArbitrary);
+    });
+
+    it('should be able to construct mutually recursive arbitraries', () => {
+      // Arrange / Act
+      const { arb1, arb2 } = letrec((tie) => ({
+        arb1: tie('arb2'),
+        arb2: tie('arb1'),
+      }));
+
+      // Assert
+      expect(arb1).toBeDefined();
+      expect(arb2).toBeDefined();
+    });
+
+    it('should apply tie correctly', () => {
+      // Arrange
+      const { instance: expectedArb } = fakeNextArbitrary();
+
+      // Act
+      const { arb1, arb2, arb3 } = letrec((tie) => ({
+        arb1: tie('arb2'),
+        arb2: tie('arb3'),
+        arb3: expectedArb,
+      }));
+
+      // Assert
+      expect(arb1).toBeInstanceOf(LazyArbitrary);
+      expect(arb2).toBeInstanceOf(LazyArbitrary);
+      expect(arb3).not.toBeInstanceOf(LazyArbitrary);
+      expect(((arb1 as any) as LazyArbitrary<unknown>).underlying).toBe(arb2);
+      expect(((arb2 as any) as LazyArbitrary<unknown>).underlying).toBe(arb3);
+      expect(arb3).toBe(expectedArb);
+    });
+
+    it('should apply tie the same way for a reversed declaration', () => {
+      // Arrange
+      const { instance: expectedArb } = fakeNextArbitrary();
+
+      // Act
+      const { arb1, arb2, arb3 } = letrec((tie) => ({
+        // Same scenario as 'should apply tie correctly'
+        // except we declared arb3 > arb2 > arb1
+        // instead of arb1 > arb2 > arb3
+        arb3: expectedArb,
+        arb2: tie('arb3'),
+        arb1: tie('arb2'),
+      }));
+
+      // Assert
+      expect(arb1).toBeInstanceOf(LazyArbitrary);
+      expect(arb2).toBeInstanceOf(LazyArbitrary);
+      expect(arb3).not.toBeInstanceOf(LazyArbitrary);
+      expect(((arb1 as any) as LazyArbitrary<unknown>).underlying).toBe(arb2);
+      expect(((arb2 as any) as LazyArbitrary<unknown>).underlying).toBe(arb3);
+      expect(arb3).toBe(expectedArb);
+    });
+  });
+
+  describe('generate', () => {
+    it('should be able to delay calls to tie to generate', () => {
+      // Arrange
+      const biasFactor = 69;
+      const { instance: simpleArb, generate } = fakeNextArbitrary();
+      generate.mockReturnValueOnce(new NextValue(null));
+      const { instance: mrng } = fakeRandom();
+
+      // Act
+      const { arb1 } = letrec((tie) => {
+        const { instance: simpleArb2, generate: generate2 } = fakeNextArbitrary();
+        generate2.mockImplementation((...args) => tie('arb2').generate(...args));
+        return {
+          arb1: simpleArb2,
+          arb2: simpleArb,
+        };
+      });
+      expect(generate).not.toHaveBeenCalled();
+      arb1.generate(mrng, biasFactor);
+
+      // Assert
+      expect(generate).toHaveBeenCalledTimes(1);
+      expect(generate).toHaveBeenCalledWith(mrng, biasFactor);
+    });
+
+    it('should throw on generate if tie receives an invalid parameter', () => {
+      // Arrange
+      const biasFactor = 42;
+      const { arb1 } = letrec((tie) => ({
+        arb1: tie('missing'),
+      }));
+      const { instance: mrng } = fakeRandom();
+
+      // Act / Assert
+      expect(() => arb1.generate(mrng, biasFactor)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"missing\\" not correctly initialized"`
+      );
+    });
+
+    it('should throw on generate if tie receives an invalid parameter after creation', () => {
+      // Arrange
+      const biasFactor = 42;
+      const { arb1 } = letrec((tie) => {
+        const { instance: simpleArb, generate } = fakeNextArbitrary();
+        generate.mockImplementation((...args) => tie('missing').generate(...args));
+        return {
+          arb1: simpleArb,
+        };
+      });
+      const { instance: mrng } = fakeRandom();
+
+      // Act / Assert
+      expect(() => arb1.generate(mrng, biasFactor)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"missing\\" not correctly initialized"`
+      );
+    });
+
+    it('should accept "reserved" keys as output of builder', () => {
+      // Arrange
+      const biasFactor = 42;
+      const { instance: simpleArb, generate } = fakeNextArbitrary();
+      generate.mockReturnValueOnce(new NextValue(null));
+      const { tie } = letrec((tie) => ({
+        tie: tie('__proto__'),
+        ['__proto__']: tie('__defineGetter__​​'),
+        ['__defineGetter__​​']: tie('__defineSetter__​​'),
+        ['__defineSetter__​​']: tie('__lookupGetter__​​'),
+        ['__lookupGetter__​​']: tie('__lookupSetter__​​'),
+        ['__lookupSetter__​​']: tie('constructor​​'),
+        ['constructor​​']: tie('hasOwnProperty​​'),
+        ['hasOwnProperty​​']: tie('isPrototypeOf​​'),
+        ['isPrototypeOf​​']: tie('propertyIsEnumerable​​'),
+        ['propertyIsEnumerable​​']: tie('toLocaleString​​'),
+        ['toLocaleString​​']: tie('toSource​​'),
+        ['toSource​​']: tie('toString​​'),
+        ['toString​​']: tie('valueOf'),
+        ['valueOf']: simpleArb,
+      }));
+      const { instance: mrng } = fakeRandom();
+
+      // Act
+      expect(generate).not.toHaveBeenCalled();
+      tie.generate(mrng, biasFactor);
+
+      // Assert
+      expect(generate).toHaveBeenCalledTimes(1);
+      expect(generate).toHaveBeenCalledWith(mrng, biasFactor);
+    });
+
+    it('should accept builders producing objects based on Object.create(null)', () => {
+      // Arrange
+      const biasFactor = 42;
+      const { instance: simpleArb, generate } = fakeNextArbitrary();
+      generate.mockReturnValueOnce(new NextValue(null));
+      const { a } = letrec((tie) =>
+        Object.assign(Object.create(null), {
+          a: tie('b'),
+          b: simpleArb,
+        })
+      );
+      const { instance: mrng } = fakeRandom();
+
+      // Act
+      expect(generate).not.toHaveBeenCalled();
+      a.generate(mrng, biasFactor);
+
+      // Assert
+      expect(generate).toHaveBeenCalledTimes(1);
+      expect(generate).toHaveBeenCalledWith(mrng, biasFactor);
+    });
+  });
+
+  describe('canGenerate', () => {
+    it.each`
+      expectedStatus
+      ${false}
+      ${true}
+    `('should call canGenerate on the targets', ({ expectedStatus }) => {
+      // Arrange
+      const expectedValue = Symbol();
+      const { instance: simpleArb, canGenerate } = fakeNextArbitrary();
+      canGenerate.mockReturnValueOnce(expectedStatus);
+      const { arb1 } = letrec((tie) => {
+        return {
+          arb1: tie('arb2'),
+          arb2: simpleArb,
+        };
+      });
+
+      // Act
+      const out = arb1.canGenerate(expectedValue);
+
+      // Assert
+      expect(canGenerate).toHaveBeenCalledTimes(1);
+      expect(canGenerate).toHaveBeenCalledWith(expectedValue);
+      expect(out).toBe(expectedStatus);
+    });
+
+    it('should throw on canGenerate if tie receives an invalid parameter', () => {
+      // Arrange
+      const expectedValue = Symbol();
+      const { arb1 } = letrec((tie) => ({
+        arb1: tie('missing'),
+      }));
+
+      // Act / Assert
+      expect(() => arb1.canGenerate(expectedValue)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"missing\\" not correctly initialized"`
+      );
+    });
+  });
+
+  describe('shrink', () => {
+    it('should call shrink on the targets', () => {
+      // Arrange
+      const expectedValue = Symbol();
+      const expectedContext = Symbol();
+      const expectedStream = Stream.of(new NextValue(Symbol()));
+      const { instance: simpleArb, shrink } = fakeNextArbitrary();
+      shrink.mockReturnValueOnce(expectedStream);
+      const { arb1 } = letrec((tie) => {
+        return {
+          arb1: tie('arb2'),
+          arb2: simpleArb,
+        };
+      });
+
+      // Act
+      const out = arb1.shrink(expectedValue, expectedContext);
+
+      // Assert
+      expect(shrink).toHaveBeenCalledTimes(1);
+      expect(shrink).toHaveBeenCalledWith(expectedValue, expectedContext);
+      expect(out).toBe(expectedStream);
+    });
+
+    it('should throw on shrink if tie receives an invalid parameter', () => {
+      // Arrange
+      const expectedValue = Symbol();
+      const expectedContext = Symbol();
+      const { arb1 } = letrec((tie) => ({
+        arb1: tie('missing'),
+      }));
+
+      // Act / Assert
+      expect(() => arb1.shrink(expectedValue, expectedContext)).toThrowErrorMatchingInlineSnapshot(
+        `"Lazy arbitrary \\"missing\\" not correctly initialized"`
+      );
+    });
+  });
+});
+
+describe('letrec (integration)', () => {
+  const letrecBuilder = () => {
+    const { a } = letrec((tie) => ({
+      a: tie('b'),
+      b: tie('c'),
+      c: new FakeIntegerArbitrary(),
+    }));
+    return a;
+  };
+
+  it('should generate the values as-if we directly called the target arbitrary', () => {
+    assertGenerateEquivalentTo(letrecBuilder, () => new FakeIntegerArbitrary(), {
+      isEqualContext: (c1, c2) => {
+        expect(c2).toEqual(c1);
+      },
+    });
+  });
+
+  it('should generate the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(letrecBuilder);
+  });
+
+  it('should recognize values that would have been generated using it during generate', () => {
+    assertGenerateProducesValuesFlaggedAsCanGenerate(letrecBuilder);
+  });
+
+  it('should shrink towards the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(letrecBuilder);
+  });
+
+  it('should be able to shrink without any context if underlyings do', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(letrecBuilder);
+  });
+
+  it('should recognize values that would have been generated using it during shrink', () => {
+    assertShrinkProducesValuesFlaggedAsCanGenerate(letrecBuilder);
+  });
+});
diff --git a/test/unit/arbitrary/memo.spec.ts b/test/unit/arbitrary/memo.spec.ts
new file mode 100644
index 00000000..1e8248f4
--- /dev/null
+++ b/test/unit/arbitrary/memo.spec.ts
@@ -0,0 +1,130 @@
+import { memo as memoOld } from '../../../src/arbitrary/memo';
+import { NextArbitrary } from '../../../src/check/arbitrary/definition/NextArbitrary';
+import { convertFromNext, convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { fakeNextArbitrary } from '../check/arbitrary/generic/NextArbitraryHelpers';
+import { fakeRandom } from '../check/arbitrary/generic/RandomHelpers';
+
+type NextMemo<T> = (maxDepth?: number) => NextArbitrary<T>;
+
+function memo<T>(builder: (maxDepth: number) => NextArbitrary<T>): NextMemo<T> {
+  const memoed = memoOld((d) => convertFromNext(builder(d)));
+  return (maxDepth?: number) => convertToNext(memoed(maxDepth));
+}
+
+describe('memo', () => {
+  it('should return the produced instance of arbitrary', () => {
+    // Arrange
+    const { instance: expectedArb } = fakeNextArbitrary();
+    const builder = memo(() => expectedArb);
+
+    // Act
+    const arb = builder();
+
+    // Assert
+    expect(arb).toBe(expectedArb);
+  });
+
+  it('should cache arbitraries associated to each depth', () => {
+    // Arrange
+    const builder = memo(() => fakeNextArbitrary().instance);
+
+    // Act
+    const arb = builder();
+
+    // Assert
+    expect(builder(10)).toBe(builder(10));
+    expect(builder(42)).toBe(builder(42));
+    expect(builder(65500)).toBe(builder(65500));
+    expect(builder()).toBe(arb);
+  });
+
+  it('should instantiate new arbitraries for each depth', () => {
+    // Arrange
+    const builder = memo(() => fakeNextArbitrary().instance);
+
+    // Act
+    const b10 = builder(10);
+    const b42 = builder(42);
+
+    // Assert
+    expect(b10).not.toBe(b42);
+  });
+
+  it('should consider no depth as depth 10', () => {
+    // Arrange
+    const builder = memo(() => fakeNextArbitrary().instance);
+
+    // Act
+    const bDefault = builder();
+    const b10 = builder(10);
+
+    // Assert
+    expect(bDefault).toBe(b10);
+  });
+
+  it('should automatically decrease depth for self recursive', () => {
+    // Arrange
+    const { instance: expectedArb } = fakeNextArbitrary();
+    const memoFun = jest.fn();
+    const builder = memo(memoFun);
+    memoFun.mockImplementation((n) => (n <= 6 ? expectedArb : builder()));
+
+    // Act
+    builder();
+
+    // Assert
+    expect(memoFun.mock.calls).toEqual([[10], [9], [8], [7], [6]]);
+  });
+
+  it('should automatically interleave decrease depth for mutually recursive', () => {
+    // Arrange
+    const { instance: expectedArb } = fakeNextArbitrary();
+    const memoFunA = jest.fn();
+    const memoFunB = jest.fn();
+    const builderA = memo(memoFunA);
+    const builderB = memo(memoFunB);
+    memoFunA.mockImplementation((n) => (n <= 6 ? expectedArb : builderB()));
+    memoFunB.mockImplementation((n) => (n <= 6 ? expectedArb : builderA()));
+
+    // Act
+    builderA();
+
+    // Assert
+    expect(memoFunA.mock.calls).toEqual([[10], [8], [6]]);
+    expect(memoFunB.mock.calls).toEqual([[9], [7]]);
+  });
+
+  it('should be able to override decrease depth', () => {
+    // Arrange
+    const { instance: expectedArb } = fakeNextArbitrary();
+    const memoFun = jest.fn();
+    const builder = memo(memoFun);
+    memoFun.mockImplementation((n) => (n <= 0 ? expectedArb : builder(n - 3)));
+
+    // Act
+    builder();
+
+    // Assert
+    expect(memoFun.mock.calls).toEqual([[10], [7], [4], [1], [-2]]);
+  });
+
+  it('should be able to delay calls to sub-builders', () => {
+    // Arrange
+    const expectedBiasedFactor = 42;
+    const { instance: simpleArb, generate } = fakeNextArbitrary();
+    const { instance: mrng } = fakeRandom();
+
+    // Act
+    const builderA = memo(() => {
+      const { instance: arbA, generate: generateA } = fakeNextArbitrary();
+      generateA.mockImplementation((mrng, biasedFactor) => builderB().generate(mrng, biasedFactor));
+      return arbA;
+    });
+    const builderB = memo(() => simpleArb);
+    expect(generate).not.toHaveBeenCalled();
+    builderA().generate(mrng, expectedBiasedFactor);
+
+    // Assert
+    expect(generate).toHaveBeenCalledWith(mrng, expectedBiasedFactor);
+  });
+});
diff --git a/test/unit/check/arbitrary/MemoArbitrary.utest.spec.ts b/test/unit/check/arbitrary/MemoArbitrary.utest.spec.ts
deleted file mode 100644
index 4a48db57..00000000
--- a/test/unit/check/arbitrary/MemoArbitrary.utest.spec.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { memo as memoOld } from '../../../../src/check/arbitrary/MemoArbitrary';
-import { NextArbitrary } from '../../../../src/check/arbitrary/definition/NextArbitrary';
-import { convertFromNext, convertToNext } from '../../../../src/check/arbitrary/definition/Converters';
-
-import { fakeNextArbitrary } from './generic/NextArbitraryHelpers';
-import * as stubRng from '../../stubs/generators';
-
-type NextMemo<T> = (maxDepth?: number) => NextArbitrary<T>;
-
-const mrngNoCall = stubRng.mutable.nocall();
-function memo<T>(builder: (maxDepth: number) => NextArbitrary<T>): NextMemo<T> {
-  const memoed = memoOld((d) => convertFromNext(builder(d)));
-  return (maxDepth?: number) => convertToNext(memoed(maxDepth));
-}
-
-describe('memo', () => {
-  describe('builder', () => {
-    it('should return the produced instance of arbitrary', () => {
-      // Arrange
-      const { instance: expectedArb } = fakeNextArbitrary();
-      const builder = memo(() => expectedArb);
-
-      // Act
-      const arb = builder();
-
-      // Assert
-      expect(arb).toBe(expectedArb);
-    });
-
-    it('should cache arbitraries associated to each depth', () => {
-      // Arrange
-      const builder = memo(() => fakeNextArbitrary().instance);
-
-      // Act
-      const arb = builder();
-
-      // Assert
-      expect(builder(10)).toBe(builder(10));
-      expect(builder(42)).toBe(builder(42));
-      expect(builder(65500)).toBe(builder(65500));
-      expect(builder()).toBe(arb);
-    });
-
-    it('should instantiate new arbitraries for each depth', () => {
-      // Arrange
-      const builder = memo(() => fakeNextArbitrary().instance);
-
-      // Act
-      const b10 = builder(10);
-      const b42 = builder(42);
-
-      // Assert
-      expect(b10).not.toBe(b42);
-    });
-
-    it('should consider no depth as depth 10', () => {
-      // Arrange
-      const builder = memo(() => fakeNextArbitrary().instance);
-
-      // Act
-      const bDefault = builder();
-      const b10 = builder(10);
-
-      // Assert
-      expect(bDefault).toBe(b10);
-    });
-
-    it('should automatically decrease depth for self recursive', () => {
-      // Arrange
-      const { instance: expectedArb } = fakeNextArbitrary();
-      const memoFun = jest.fn();
-      const builder = memo(memoFun);
-      memoFun.mockImplementation((n) => (n <= 6 ? expectedArb : builder()));
-
-      // Act
-      builder();
-
-      // Assert
-      expect(memoFun.mock.calls).toEqual([[10], [9], [8], [7], [6]]);
-    });
-
-    it('should automatically interleave decrease depth for mutually recursive', () => {
-      // Arrange
-      const { instance: expectedArb } = fakeNextArbitrary();
-      const memoFunA = jest.fn();
-      const memoFunB = jest.fn();
-      const builderA = memo(memoFunA);
-      const builderB = memo(memoFunB);
-      memoFunA.mockImplementation((n) => (n <= 6 ? expectedArb : builderB()));
-      memoFunB.mockImplementation((n) => (n <= 6 ? expectedArb : builderA()));
-
-      // Act
-      builderA();
-
-      // Assert
-      expect(memoFunA.mock.calls).toEqual([[10], [8], [6]]);
-      expect(memoFunB.mock.calls).toEqual([[9], [7]]);
-    });
-
-    it('should be able to override decrease depth', () => {
-      // Arrange
-      const { instance: expectedArb } = fakeNextArbitrary();
-      const memoFun = jest.fn();
-      const builder = memo(memoFun);
-      memoFun.mockImplementation((n) => (n <= 0 ? expectedArb : builder(n - 3)));
-
-      // Act
-      builder();
-
-      // Assert
-      expect(memoFun.mock.calls).toEqual([[10], [7], [4], [1], [-2]]);
-    });
-
-    it('should be able to delay calls to sub-builders', () => {
-      // Arrange
-      const expectedBiasedFactor = 42;
-      const { instance: simpleArb, generate } = fakeNextArbitrary();
-
-      // Act
-      const builderA = memo(() => {
-        const { instance: arbA, generate: generateA } = fakeNextArbitrary();
-        generateA.mockImplementation((mrng, biasedFactor) => builderB().generate(mrng, biasedFactor));
-        return arbA;
-      });
-      const builderB = memo(() => simpleArb);
-      expect(generate).not.toHaveBeenCalled();
-      builderA().generate(mrngNoCall, expectedBiasedFactor);
-
-      // Assert
-      expect(generate).toHaveBeenCalledWith(mrngNoCall, expectedBiasedFactor);
-    });
-  });
-});