Repository: dubzzz/fast-check
Pull request #1230 (2020-12-08 20:56:43)

💚 Reduce flakyness of object boxing coverage
<!-- 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: *coverage*

(✔️: yes, ❌: no)

## Potential impacts

None

codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/dubzzz/fast-check/pr/1230/builds/80919) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 7263aa21fbbc398f1d548ce7ce4543c7f40ce3f4:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-1s6jd)| Configuration |
|[fast-check-examples](https://codesandbox.io/s/fast-check-examples-82zxb)| Configuration |


Patch:
diff --git a/src/check/arbitrary/ObjectArbitrary.ts b/src/check/arbitrary/ObjectArbitrary.ts
index 112a12c9..1f50308e 100644
--- a/src/check/arbitrary/ObjectArbitrary.ts
+++ b/src/check/arbitrary/ObjectArbitrary.ts
@@ -83,6 +83,25 @@ export interface JsonSharedConstraints {
   maxDepth?: number;
 }
 
+/** @internal */
+export function boxArbitrary(arb: Arbitrary<unknown>): Arbitrary<unknown> {
+  return arb.map((v) => {
+    switch (typeof v) {
+      case 'boolean':
+        // tslint:disable-next-line:no-construct
+        return new Boolean(v);
+      case 'number':
+        // tslint:disable-next-line:no-construct
+        return new Number(v);
+      case 'string':
+        // tslint:disable-next-line:no-construct
+        return new String(v);
+      default:
+        return v;
+    }
+  });
+}
+
 /** @internal */
 class QualifiedObjectConstraints {
   constructor(
@@ -112,23 +131,7 @@ class QualifiedObjectConstraints {
   }
 
   private static boxArbitraries(arbs: Arbitrary<unknown>[]): Arbitrary<unknown>[] {
-    return arbs.map((arb) =>
-      arb.map((v) => {
-        switch (typeof v) {
-          case 'boolean':
-            // tslint:disable-next-line:no-construct
-            return new Boolean(v);
-          case 'number':
-            // tslint:disable-next-line:no-construct
-            return new Number(v);
-          case 'string':
-            // tslint:disable-next-line:no-construct
-            return new String(v);
-          default:
-            return v;
-        }
-      })
-    );
+    return arbs.map((arb) => boxArbitrary(arb));
   }
 
   private static boxArbitrariesIfNeeded(arbs: Arbitrary<unknown>[], boxEnabled: boolean): Arbitrary<unknown>[] {


Test patch:
diff --git a/test/unit/check/arbitrary/ObjectArbitrary.spec.ts b/test/unit/check/arbitrary/ObjectArbitrary.spec.ts
index ebcf0086..1d7e4324 100644
--- a/test/unit/check/arbitrary/ObjectArbitrary.spec.ts
+++ b/test/unit/check/arbitrary/ObjectArbitrary.spec.ts
@@ -11,9 +11,14 @@ import {
   json,
   unicodeJson,
   ObjectConstraints,
+  boxArbitrary,
 } from '../../../../src/check/arbitrary/ObjectArbitrary';
 
 import { Random } from '../../../../src/random/generator/Random';
+import { arbitraryFor } from './generic/ArbitraryBuilder';
+import * as stubRng from '../../stubs/generators';
+
+const mrng = () => stubRng.mutable.nocall();
 
 declare function BigInt(n: number | bigint | string): bigint;
 
@@ -427,4 +432,56 @@ describe('ObjectArbitrary', () => {
         })
       ));
   });
+
+  describe('boxArbitrary (internal)', () => {
+    it.each`
+      type         | arbitrary
+      ${'number'}  | ${fc.double({ next: true })}
+      ${'boolean'} | ${fc.boolean()}
+      ${'string'}  | ${fc.string()}
+    `('Should box any $type', ({ arbitrary }) =>
+      fc.assert(
+        fc.property(arbitrary, (originalValue) => {
+          const boxedArbitrary = boxArbitrary(arbitraryFor([{ value: originalValue }]));
+          const { value } = boxedArbitrary.generate(mrng());
+          expect(typeof value).toBe('object');
+          expect(value).not.toBe(originalValue);
+          if (value != originalValue) {
+            fail(`Expected: ${fc.stringify(originalValue)}, Got: ${fc.stringify(value)}`);
+          }
+        })
+      )
+    );
+
+    it('Should not box unboxable types', () =>
+      fc.assert(
+        fc.property(
+          fc.anything({
+            withBigInt: typeof BigInt !== 'undefined',
+            withBoxedValues: true,
+            withDate: true,
+            withMap: true,
+            withNullPrototype: true,
+            withObjectString: true,
+            withSet: true,
+          }),
+          (originalValue: any) => {
+            fc.pre(typeof originalValue !== 'number');
+            fc.pre(typeof originalValue !== 'boolean');
+            fc.pre(typeof originalValue !== 'string');
+
+            // We ensure originalValue cannot be boxed just in case we missed some cases
+            if (originalValue != null && typeof originalValue !== 'object') {
+              const Ctor: { new (v: typeof originalValue): unknown } = originalValue.constructor;
+              expect(Ctor).toBeInstanceOf(Function);
+              expect(() => new Ctor(originalValue)).toThrow(); //others like symbol, bigint...
+            }
+
+            const boxedArbitrary = boxArbitrary(arbitraryFor([{ value: originalValue }]));
+            const { value } = boxedArbitrary.generate(mrng());
+            expect(value).toBe(originalValue);
+          }
+        )
+      ));
+  });
 });