Repository: dubzzz/fast-check
Pull request #1912 (2021-05-26 19:07:47)

✨ Add built-in unmapper support onto `record`
<!-- Context of the PR: short description and potentially linked issues -->

<!-- ...a few words to describe the content of this PR...               -->
<!-- ... -->

<!-- Type of PR: [ ] unchecked / [ ] checked -->
**_Category:_**

- [x] ✨ Introduce new features
- [ ] 📝 Add or update documentation
- [ ] ✅ Add or update tests
- [ ] 🐛 Fix a bug
- [ ] 🏷️ Add or update types
- [ ] _Other(s):_ ...
<!-- Don't forget to add the gitmoji icon in the name of the PR -->
<!-- See: https://gitmoji.dev/                                  -->

<!-- Fixing bugs, adding feastures... may impact existing ones           -->
<!-- in order to track potential issues that could be related to your PR -->
<!-- please check the impacts and describe more precisely what to expect -->

**_Potential impacts:_**

<!-- Generated values: Can your change impact any of the existing generators in terms of generated values, if so which ones? when? -->
<!-- Shrink values:    Can your change impact any of the existing generators in terms of shrink values, if so which ones? when? -->
<!-- Performance:      Can it require some typings changes on user side? Please give more details -->
<!-- Typings:          Is there a potential performance impact? In which cases? -->
- [ ] Generated values
- [ ] Shrink values
- [ ] Performance
- [ ] Typings
- [ ] _Other(s):_ ...


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/1912/builds/134090) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit e12eb81fd2ac1e752dd4fedc871960f1d85ba952:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-70v4y)| Configuration |
|[fast-check-examples](https://codesandbox.io/s/fast-check-examples-lfpg5)| Configuration |


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a1b74389..2d5d6523 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -15,7 +15,7 @@
 <!-- Don't forget to add the gitmoji icon in the name of the PR -->
 <!-- See: https://gitmoji.dev/                                  -->
 
-<!-- Fixing bugs, adding feastures... may impact existing ones           -->
+<!-- Fixing bugs, adding features... may impact existing ones           -->
 <!-- in order to track potential issues that could be related to your PR -->
 <!-- please check the impacts and describe more precisely what to expect -->
 
diff --git a/src/arbitrary/_internals/ArrayArbitrary.ts b/src/arbitrary/_internals/ArrayArbitrary.ts
index 58e51b46..fd2360cf 100644
--- a/src/arbitrary/_internals/ArrayArbitrary.ts
+++ b/src/arbitrary/_internals/ArrayArbitrary.ts
@@ -121,7 +121,7 @@ export class ArrayArbitrary<T> extends NextArbitrary<T[]> {
     return { size: targetSizeValue.value, biasFactorItems: biasFactor };
   }
 
-  canGenerate(value: unknown): value is T[] {
+  canShrinkWithoutContext(value: unknown): value is T[] {
     if (!Array.isArray(value) || this.minLength > value.length || value.length > this.maxLength) {
       return false;
     }
@@ -130,7 +130,7 @@ export class ArrayArbitrary<T> extends NextArbitrary<T[]> {
         // sparse array cannot be produced by this instance
         return false;
       }
-      if (!this.arb.canGenerate(value[index])) {
+      if (!this.arb.canShrinkWithoutContext(value[index])) {
         // item at index cannot be produced by our arbitrary
         return false;
       }
diff --git a/src/arbitrary/_internals/BigIntArbitrary.ts b/src/arbitrary/_internals/BigIntArbitrary.ts
index 28cebb55..53bac8d7 100644
--- a/src/arbitrary/_internals/BigIntArbitrary.ts
+++ b/src/arbitrary/_internals/BigIntArbitrary.ts
@@ -27,7 +27,7 @@ export class BigIntArbitrary extends NextArbitrary<bigint> {
     return id < 0 ? ranges[0] : ranges[id + 1];
   }
 
-  canGenerate(value: unknown): value is bigint {
+  canShrinkWithoutContext(value: unknown): value is bigint {
     return typeof value === 'bigint' && this.min <= value && value <= this.max;
   }
 
diff --git a/src/arbitrary/_internals/CloneArbitrary.ts b/src/arbitrary/_internals/CloneArbitrary.ts
index 042ed254..4a24471c 100644
--- a/src/arbitrary/_internals/CloneArbitrary.ts
+++ b/src/arbitrary/_internals/CloneArbitrary.ts
@@ -23,7 +23,7 @@ export class CloneArbitrary<T> extends NextArbitrary<T[]> {
     return this.wrapper(items);
   }
 
-  canGenerate(value: unknown): value is T[] {
+  canShrinkWithoutContext(value: unknown): value is T[] {
     if (!Array.isArray(value) || value.length !== this.numValues) {
       return false;
     }
@@ -33,12 +33,12 @@ export class CloneArbitrary<T> extends NextArbitrary<T[]> {
     for (let index = 1; index < value.length; ++index) {
       if (!Object.is(value[0], value[index])) {
         // We don't relally know:
-        // >  Properly implementing canGenerate for general case is really complex.
+        // >  Properly implementing canShrinkWithoutContext for general case is really complex.
         // >  If values generated by `this.arb` cannot be compared using `Object.is` then the check is impossible.
         return false;
       }
     }
-    return this.arb.canGenerate(value[0]);
+    return this.arb.canShrinkWithoutContext(value[0]);
   }
 
   shrink(value: T[], context?: unknown): Stream<NextValue<T[]>> {
diff --git a/src/arbitrary/_internals/ConstantArbitrary.ts b/src/arbitrary/_internals/ConstantArbitrary.ts
index f39b35ae..4f8e992a 100644
--- a/src/arbitrary/_internals/ConstantArbitrary.ts
+++ b/src/arbitrary/_internals/ConstantArbitrary.ts
@@ -16,7 +16,7 @@ export class ConstantArbitrary<T> extends NextArbitrary<T> {
     const idx = mrng.nextInt(0, this.values.length - 1);
     return new NextValue(this.values[idx], idx, () => cloneIfNeeded(this.values[idx]));
   }
-  canGenerate(value: unknown): value is T {
+  canShrinkWithoutContext(value: unknown): value is T {
     for (let idx = 0; idx !== this.values.length; ++idx) {
       if (Object.is(this.values[idx], value)) {
         return true;
diff --git a/src/arbitrary/_internals/FrequencyArbitrary.ts b/src/arbitrary/_internals/FrequencyArbitrary.ts
index 933f93b1..9b547e3d 100644
--- a/src/arbitrary/_internals/FrequencyArbitrary.ts
+++ b/src/arbitrary/_internals/FrequencyArbitrary.ts
@@ -75,8 +75,8 @@ export class FrequencyArbitrary<T> extends NextArbitrary<T> {
     throw new Error(`Unable to generate from fc.frequency`);
   }
 
-  canGenerate(value: unknown): value is T {
-    return this.canGenerateIndex(value) !== -1;
+  canShrinkWithoutContext(value: unknown): value is T {
+    return this.canShrinkWithoutContextIndex(value) !== -1;
   }
 
   shrink(value: T, context?: unknown): Stream<NextValue<T>> {
@@ -101,7 +101,7 @@ export class FrequencyArbitrary<T> extends NextArbitrary<T> {
       }
       return originalShrinks;
     }
-    const potentialSelectedIndex = this.canGenerateIndex(value);
+    const potentialSelectedIndex = this.canShrinkWithoutContextIndex(value);
     if (potentialSelectedIndex === -1) {
       return Stream.nil(); // No arbitrary found to accept this value
     }
@@ -128,15 +128,15 @@ export class FrequencyArbitrary<T> extends NextArbitrary<T> {
   }
 
   /** Extract the index of the generator that would have been able to gennrate the value */
-  private canGenerateIndex(value: unknown): number {
+  private canShrinkWithoutContextIndex(value: unknown): number {
     if (this.mustGenerateFirst()) {
-      return this.warbs[0].arbitrary.canGenerate(value) ? 0 : -1;
+      return this.warbs[0].arbitrary.canShrinkWithoutContext(value) ? 0 : -1;
     }
     try {
       ++this.context.depth; // increase depth
       for (let idx = 0; idx !== this.warbs.length; ++idx) {
         const warb = this.warbs[idx];
-        if (warb.weight !== 0 && warb.arbitrary.canGenerate(value)) {
+        if (warb.weight !== 0 && warb.arbitrary.canShrinkWithoutContext(value)) {
           return idx;
         }
       }
diff --git a/src/arbitrary/_internals/IntegerArbitrary.ts b/src/arbitrary/_internals/IntegerArbitrary.ts
index 88a2ad1c..61c77e0d 100644
--- a/src/arbitrary/_internals/IntegerArbitrary.ts
+++ b/src/arbitrary/_internals/IntegerArbitrary.ts
@@ -16,7 +16,7 @@ export class IntegerArbitrary extends NextArbitrary<number> {
     return new NextValue(mrng.nextInt(range.min, range.max), undefined);
   }
 
-  canGenerate(value: unknown): value is number {
+  canShrinkWithoutContext(value: unknown): value is number {
     return (
       typeof value === 'number' &&
       Number.isInteger(value) &&
diff --git a/src/arbitrary/_internals/LazyArbitrary.ts b/src/arbitrary/_internals/LazyArbitrary.ts
index b7d04436..3a79c844 100644
--- a/src/arbitrary/_internals/LazyArbitrary.ts
+++ b/src/arbitrary/_internals/LazyArbitrary.ts
@@ -15,11 +15,11 @@ export class LazyArbitrary<T> extends NextArbitrary<T> {
     }
     return this.underlying.generate(mrng, biasFactor);
   }
-  canGenerate(value: unknown): value is T {
+  canShrinkWithoutContext(value: unknown): value is T {
     if (!this.underlying) {
       throw new Error(`Lazy arbitrary ${JSON.stringify(this.name)} not correctly initialized`);
     }
-    return this.underlying.canGenerate(value);
+    return this.underlying.canShrinkWithoutContext(value);
   }
   shrink(value: T, context?: unknown): Stream<NextValue<T>> {
     if (!this.underlying) {
diff --git a/src/arbitrary/_internals/MixedCaseArbitrary.ts b/src/arbitrary/_internals/MixedCaseArbitrary.ts
index 6a83ce62..bc1bb11d 100644
--- a/src/arbitrary/_internals/MixedCaseArbitrary.ts
+++ b/src/arbitrary/_internals/MixedCaseArbitrary.ts
@@ -102,7 +102,7 @@ export class MixedCaseArbitrary extends NextArbitrary<string> {
     return new NextValue(chars.join(''), this.buildContextFor(rawStringNextValue, flagsNextValue));
   }
 
-  canGenerate(value: unknown): value is string {
+  canShrinkWithoutContext(value: unknown): value is string {
     // Not implemented yet
     return false;
   }
diff --git a/src/arbitrary/_internals/SchedulerArbitrary.ts b/src/arbitrary/_internals/SchedulerArbitrary.ts
index d5145fdf..2245c21e 100644
--- a/src/arbitrary/_internals/SchedulerArbitrary.ts
+++ b/src/arbitrary/_internals/SchedulerArbitrary.ts
@@ -30,7 +30,7 @@ export class SchedulerArbitrary<TMetaData> extends NextArbitrary<Scheduler<TMeta
     return new NextValue(new SchedulerImplem<TMetaData>(this.act, buildNextTaskIndex(mrng.clone())));
   }
 
-  canGenerate(value: unknown): value is Scheduler<TMetaData> {
+  canShrinkWithoutContext(value: unknown): value is Scheduler<TMetaData> {
     // Not supported yet
     return false;
   }
diff --git a/src/arbitrary/_internals/StreamArbitrary.ts b/src/arbitrary/_internals/StreamArbitrary.ts
index 8fc246a2..e6cac5b5 100644
--- a/src/arbitrary/_internals/StreamArbitrary.ts
+++ b/src/arbitrary/_internals/StreamArbitrary.ts
@@ -29,7 +29,7 @@ export class StreamArbitrary<T> extends NextArbitrary<Stream<T>> {
     return new NextValue(enrichedProducer());
   }
 
-  canGenerate(value: unknown): value is Stream<T> {
+  canShrinkWithoutContext(value: unknown): value is Stream<T> {
     // Knowing if we can generate or not an infinite stream would require to iterate over it
     // (until its "end")
     return false;
diff --git a/src/arbitrary/_internals/TupleArbitrary.ts b/src/arbitrary/_internals/TupleArbitrary.ts
index 9711b7b5..535b7257 100644
--- a/src/arbitrary/_internals/TupleArbitrary.ts
+++ b/src/arbitrary/_internals/TupleArbitrary.ts
@@ -49,12 +49,12 @@ export class TupleArbitrary<Ts extends unknown[]> extends NextArbitrary<Ts> {
   generate(mrng: Random, biasFactor: number | undefined): NextValue<Ts> {
     return TupleArbitrary.wrapper<Ts>(this.arbs.map((a) => a.generate(mrng, biasFactor)) as ValuesArray<Ts>);
   }
-  canGenerate(value: unknown): value is Ts {
+  canShrinkWithoutContext(value: unknown): value is Ts {
     if (!Array.isArray(value) || value.length !== this.arbs.length) {
       return false;
     }
     for (let index = 0; index !== this.arbs.length; ++index) {
-      if (!this.arbs[index].canGenerate(value[index])) {
+      if (!this.arbs[index].canShrinkWithoutContext(value[index])) {
         return false;
       }
     }
diff --git a/src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.ts b/src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.ts
index 2b6628aa..e86091ce 100644
--- a/src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.ts
+++ b/src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.ts
@@ -1,7 +1,12 @@
 import { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../../../check/arbitrary/definition/Converters';
 import { option } from '../../option';
 import { tuple } from '../../tuple';
 import { EnumerableKeyOf, extractEnumerableKeys } from '../helpers/EnumerableKeysExtractor';
+import {
+  buildValuesAndSeparateKeysToObjectMapper,
+  buildValuesAndSeparateKeysToObjectUnmapper,
+} from '../mappers/ValuesAndSeparateKeysToObject';
 
 const noKeyValue: unique symbol = Symbol('no-key');
 type NoKeyType = typeof noKeyValue;
@@ -19,14 +24,10 @@ export function buildPartialRecordArbitrary<T, TKeys extends EnumerableKeyOf<T>>
     if (requiredKeys === undefined || requiredKeys.indexOf(k as TKeys) !== -1) arbs.push(requiredArbitrary);
     else arbs.push(option(requiredArbitrary, { nil: noKeyValue }));
   }
-  return tuple(...arbs).map((gs: (T[keyof T] | NoKeyType)[]): Partial<T> & Pick<T, TKeys> => {
-    const obj: Partial<Record<EnumerableKeyOf<T>, T[keyof T]>> = {};
-    for (let idx = 0; idx !== keys.length; ++idx) {
-      const valueWrapper = gs[idx];
-      if (valueWrapper !== noKeyValue) {
-        obj[keys[idx]] = valueWrapper;
-      }
-    }
-    return obj as Partial<T> & Pick<T, TKeys>;
-  });
+  return convertFromNext(
+    convertToNext(tuple(...arbs)).map(
+      buildValuesAndSeparateKeysToObjectMapper<T, NoKeyType>(keys, noKeyValue),
+      buildValuesAndSeparateKeysToObjectUnmapper<T, NoKeyType>(keys, noKeyValue)
+    )
+  );
 }
diff --git a/src/arbitrary/_internals/mappers/PatternsToString.ts b/src/arbitrary/_internals/mappers/PatternsToString.ts
index 60f760b9..cb597d5d 100644
--- a/src/arbitrary/_internals/mappers/PatternsToString.ts
+++ b/src/arbitrary/_internals/mappers/PatternsToString.ts
@@ -41,7 +41,7 @@ export function patternsToStringUnmapperFor(
       // Going deeper in the tree
       for (let index = last.nextStartIndex; index <= value.length; ++index) {
         const chunk = value.substring(last.endIndexChunks, index);
-        if (patternsArb.canGenerate(chunk)) {
+        if (patternsArb.canShrinkWithoutContext(chunk)) {
           const newChunks = last.chunks.concat([chunk]);
           if (index === value.length) {
             if (newChunks.length < minLength || newChunks.length > maxLength) {
diff --git a/src/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.ts b/src/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.ts
new file mode 100644
index 00000000..2326094c
--- /dev/null
+++ b/src/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.ts
@@ -0,0 +1,52 @@
+import { EnumerableKeyOf } from '../helpers/EnumerableKeysExtractor';
+
+/** @internal */
+export function buildValuesAndSeparateKeysToObjectMapper<T, TNoKey>(keys: EnumerableKeyOf<T>[], noKeyValue: TNoKey) {
+  return function valuesAndSeparateKeysToObjectMapper(
+    gs: (T[keyof T] | TNoKey)[]
+  ): Partial<T> & Pick<T, EnumerableKeyOf<T>> {
+    const obj: Partial<Record<EnumerableKeyOf<T>, T[keyof T]>> = {};
+    for (let idx = 0; idx !== keys.length; ++idx) {
+      const valueWrapper = gs[idx];
+      if (valueWrapper !== noKeyValue) {
+        obj[keys[idx]] = valueWrapper as T[keyof T]; // not TNoKey
+      }
+    }
+    return obj as Partial<T> & Pick<T, EnumerableKeyOf<T>>;
+  };
+}
+
+/** @internal */
+export function buildValuesAndSeparateKeysToObjectUnmapper<T, TNoKey>(keys: EnumerableKeyOf<T>[], noKeyValue: TNoKey) {
+  return function valuesAndSeparateKeysToObjectUnmapper(value: unknown): (T[keyof T] | TNoKey)[] {
+    if (typeof value !== 'object' || value === null) {
+      throw new Error('Incompatible instance received: should be a non-null object');
+    }
+    if (!('constructor' in value) || value.constructor !== Object) {
+      throw new Error('Incompatible instance received: should be of exact type Object');
+    }
+    let extractedPropertiesCount = 0;
+    const extractedValues: (T[keyof T] | TNoKey)[] = [];
+    for (let idx = 0; idx !== keys.length; ++idx) {
+      const descriptor = Object.getOwnPropertyDescriptor(value, keys[idx]);
+      if (descriptor !== undefined) {
+        if (!descriptor.configurable || !descriptor.enumerable || !descriptor.writable) {
+          throw new Error('Incompatible instance received: should contain only c/e/w properties');
+        }
+        if (descriptor.get !== undefined || descriptor.set !== undefined) {
+          throw new Error('Incompatible instance received: should contain only no get/set properties');
+        }
+        ++extractedPropertiesCount;
+        extractedValues.push(descriptor.value);
+      } else {
+        extractedValues.push(noKeyValue);
+      }
+    }
+    const namePropertiesCount = Object.getOwnPropertyNames(value).length;
+    const symbolPropertiesCount = Object.getOwnPropertySymbols(value).length;
+    if (extractedPropertiesCount !== namePropertiesCount + symbolPropertiesCount) {
+      throw new Error('Incompatible instance received: should not contain extra properties');
+    }
+    return extractedValues;
+  };
+}
diff --git a/src/arbitrary/_internals/mappers/WordsToLorem.ts b/src/arbitrary/_internals/mappers/WordsToLorem.ts
index 54a202fc..3cfa95b3 100644
--- a/src/arbitrary/_internals/mappers/WordsToLorem.ts
+++ b/src/arbitrary/_internals/mappers/WordsToLorem.ts
@@ -14,8 +14,8 @@ export function wordsToJoinedStringUnmapperFor(wordsArbitrary: NextArbitrary<str
     }
     const words: string[] = [];
     for (const candidate of value.split(' ')) {
-      if (wordsArbitrary.canGenerate(candidate)) words.push(candidate);
-      else if (wordsArbitrary.canGenerate(candidate + ',')) words.push(candidate + ',');
+      if (wordsArbitrary.canShrinkWithoutContext(candidate)) words.push(candidate);
+      else if (wordsArbitrary.canShrinkWithoutContext(candidate + ',')) words.push(candidate + ',');
       else throw new Error('Unsupported word');
     }
     return words;
@@ -51,8 +51,8 @@ export function wordsToSentenceUnmapperFor(wordsArbitrary: NextArbitrary<string>
     const candidates = adaptedValue.split(' ');
     for (let idx = 0; idx !== candidates.length; ++idx) {
       const candidate = candidates[idx];
-      if (wordsArbitrary.canGenerate(candidate)) words.push(candidate);
-      else if (idx === candidates.length - 1 && wordsArbitrary.canGenerate(candidate + ','))
+      if (wordsArbitrary.canShrinkWithoutContext(candidate)) words.push(candidate);
+      else if (idx === candidates.length - 1 && wordsArbitrary.canShrinkWithoutContext(candidate + ','))
         words.push(candidate + ',');
       else throw new Error('Unsupported word');
     }
diff --git a/src/check/arbitrary/definition/ConverterToNext.ts b/src/check/arbitrary/definition/ConverterToNext.ts
index 9a440120..f0104d7b 100644
--- a/src/check/arbitrary/definition/ConverterToNext.ts
+++ b/src/check/arbitrary/definition/ConverterToNext.ts
@@ -28,7 +28,7 @@ export class ConverterToNext<T> extends NextArbitrary<T> {
     return new NextValue(g.value_, g, () => g.value);
   }
 
-  canGenerate(_value: unknown): _value is T {
+  canShrinkWithoutContext(_value: unknown): _value is T {
     return false;
   }
 
diff --git a/src/check/arbitrary/definition/NextArbitrary.ts b/src/check/arbitrary/definition/NextArbitrary.ts
index 623d828a..fe05db9f 100644
--- a/src/check/arbitrary/definition/NextArbitrary.ts
+++ b/src/check/arbitrary/definition/NextArbitrary.ts
@@ -23,28 +23,27 @@ export abstract class NextArbitrary<T> {
   abstract generate(mrng: Random, biasFactor: number | undefined): NextValue<T>;
 
   /**
-   * Check if a given value could have been generated
-   * through this instance of arbitrary.
+   * Check if a given value could be pass to `shrink` without providing any context.
    *
-   * In general, `canGenerate` is not designed to be called for each `shrink` but rather on very special cases.
-   * Its usage most be restricted to `canGenerate` or in the rare* context of a `shrink` method being called without
-   * any context. In this ill-formed case of `shrink`, `canGenerate` could be used or called if needed.
+   * In general, `canShrinkWithoutContext` is not designed to be called for each `shrink` but rather on very special cases.
+   * Its usage must be restricted to `canShrinkWithoutContext` or in the rare* contexts of a `shrink` method being called without
+   * any context. In this ill-formed case of `shrink`, `canShrinkWithoutContext` could be used or called if needed.
    *
    * *we fall in that case when fast-check is asked to shrink a value that has been provided manually by the user,
-   *  in other words: a value not coming from a call to `generate` or a normal `shrink`.
+   *  in other words: a value not coming from a call to `generate` or a normal `shrink` with context.
    *
    * @param value - Value to be assessed
    * @returns `true` if and only if the value could have been generated by this instance
    *
    * @remarks Since 2.15.0
    */
-  abstract canGenerate(value: unknown): value is T;
+  abstract canShrinkWithoutContext(value: unknown): value is T;
 
   /**
    * Shrink a value of type `T`, may rely on the context previously provided to shrink efficiently
    *
-   * Must never be called with possibly invalid values.
-   * Consider calling `canGenerate` first if you are not sure of the origin of the value.
+   * Must never be called with possibly invalid values and no context without ensuring that such call is legal
+   * by calling `canShrinkWithoutContext` first on the value.
    *
    * @param value - The value to shrink
    * @param context - Its associated context (the one returned by generate)
@@ -192,7 +191,7 @@ class ChainArbitrary<T, U> extends NextArbitrary<U> {
     const src = this.arb.generate(mrng, biasFactor);
     return this.valueChainer(src, mrng, clonedMrng, biasFactor);
   }
-  canGenerate(value: unknown): value is U {
+  canShrinkWithoutContext(value: unknown): value is U {
     // TODO Need unchainer
     return false;
   }
@@ -273,11 +272,11 @@ class MapArbitrary<T, U> extends NextArbitrary<U> {
     const g = this.arb.generate(mrng, biasFactor);
     return this.valueMapper(g);
   }
-  canGenerate(value: unknown): value is U {
+  canShrinkWithoutContext(value: unknown): value is U {
     if (this.unmapper !== undefined) {
       try {
         const unmapped = this.unmapper(value);
-        return this.arb.canGenerate(unmapped);
+        return this.arb.canShrinkWithoutContext(unmapped);
       } catch (_err) {
         return false;
       }
@@ -290,7 +289,7 @@ class MapArbitrary<T, U> extends NextArbitrary<U> {
     }
     if (this.unmapper !== undefined) {
       // As `shrink` might be called with correct values where the unmapper has sevaral choices even
-      // entries looking correct could be invalid. If the user has not called `canGenerate` first, we
+      // entries looking correct could be invalid. If the user has not called `canShrinkWithoutContext` first, we
       // might easily fall in such cases.
       //
       // Let's imagine: `mapper = (arr: string[]) => arr.join(" ")`
@@ -300,7 +299,7 @@ class MapArbitrary<T, U> extends NextArbitrary<U> {
       // unmapped value is correct for the source arbitrary. In the example above, we might imagine that
       // our source arbitrary can shrink ["Hello", "how are", "you?"] but not ["Hello", "how", "are", "you?"].
       const unmapped = this.unmapper(value);
-      if (this.arb.canGenerate(unmapped)) {
+      if (this.arb.canShrinkWithoutContext(unmapped)) {
         return this.arb.shrink(unmapped).map(this.bindValueMapper);
       }
     }
@@ -346,8 +345,8 @@ class FilterArbitrary<T, U extends T> extends NextArbitrary<U> {
       }
     }
   }
-  canGenerate(value: unknown): value is U {
-    return this.arb.canGenerate(value) && this.refinement(value);
+  canShrinkWithoutContext(value: unknown): value is U {
+    return this.arb.canShrinkWithoutContext(value) && this.refinement(value);
   }
   shrink(value: U, context?: unknown): Stream<NextValue<U>> {
     return this.arb.shrink(value, context).filter(this.bindRefinementOnValue);
@@ -365,8 +364,8 @@ class NoShrinkArbitrary<T> extends NextArbitrary<T> {
   generate(mrng: Random, biasFactor: number | undefined): NextValue<T> {
     return this.arb.generate(mrng, biasFactor);
   }
-  canGenerate(value: unknown): value is T {
-    return this.arb.canGenerate(value);
+  canShrinkWithoutContext(value: unknown): value is T {
+    return this.arb.canShrinkWithoutContext(value);
   }
   shrink(_value: T, _context?: unknown): Stream<NextValue<T>> {
     return Stream.nil();
@@ -384,8 +383,8 @@ class NoBiasArbitrary<T> extends NextArbitrary<T> {
   generate(mrng: Random, _biasFactor: number | undefined): NextValue<T> {
     return this.arb.generate(mrng, undefined);
   }
-  canGenerate(value: unknown): value is T {
-    return this.arb.canGenerate(value);
+  canShrinkWithoutContext(value: unknown): value is T {
+    return this.arb.canShrinkWithoutContext(value);
   }
   shrink(value: T, context?: unknown): Stream<NextValue<T>> {
     return this.arb.shrink(value, context);
diff --git a/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts b/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
index 97b85ae3..97a0cb03 100644
--- a/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
+++ b/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
@@ -48,7 +48,7 @@ class ArrayInt64Arbitrary extends NextArbitrary<ArrayInt64> {
     return id < 0 ? ranges[0] : ranges[id + 1];
   }
 
-  canGenerate(value: unknown): value is ArrayInt64 {
+  canShrinkWithoutContext(value: unknown): value is ArrayInt64 {
     const unsafeValue = value as ArrayInt64;
     return (
       typeof value === 'object' &&


Test patch:
diff --git a/test/unit/arbitrary/RecordArbitrary.spec.ts b/test/unit/arbitrary/RecordArbitrary.spec.ts
deleted file mode 100644
index 9b313231..00000000
--- a/test/unit/arbitrary/RecordArbitrary.spec.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import * as prand from 'pure-rand';
-import * as fc from '../../../lib/fast-check';
-
-import { constant } from '../../../src/arbitrary/constant';
-import { Arbitrary } from '../../../src/check/arbitrary/definition/Arbitrary';
-import { integer } from '../../../src/arbitrary/integer';
-import { record, RecordConstraints } from '../../../src/arbitrary/record';
-import { Random } from '../../../src/random/generator/Random';
-
-import * as genericHelper from '../check/arbitrary/generic/GenericArbitraryHelper';
-
-const keyArb: fc.Arbitrary<any> = fc
-  .tuple(fc.string(), fc.boolean())
-  .map(([name, symbol]) => (symbol ? Symbol.for(name) : name));
-
-describe('RecordArbitrary', () => {
-  describe('record', () => {
-    it('Should produce a record with missing keys', () =>
-      fc.assert(
-        fc.property(fc.set(keyArb, { minLength: 1 }), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
-          const mrng = new Random(prand.xorshift128plus(seed));
-          const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-          for (const k of keys) recordModel[k] = constant(k);
-
-          const arb = record(recordModel, { withDeletedKeys: true });
-          for (let idx = 0; idx != 1000; ++idx) {
-            const g = arb.generate(mrng).value;
-            if (!Object.prototype.hasOwnProperty.call(g, keys[missingIdx % keys.length])) return true;
-          }
-          return false;
-        })
-      ));
-    it('Should produce a record with present keys', () =>
-      fc.assert(
-        fc.property(fc.set(keyArb, { minLength: 1 }), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
-          const mrng = new Random(prand.xorshift128plus(seed));
-          const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-          for (const k of keys) {
-            recordModel[k] = constant(k);
-          }
-
-          const arb = record(recordModel, { withDeletedKeys: true });
-          for (let idx = 0; idx != 1000; ++idx) {
-            const g = arb.generate(mrng).value;
-            if (g[keys[missingIdx % keys.length]] === keys[missingIdx % keys.length]) return true;
-          }
-          return false;
-        })
-      ));
-    it('Should reject configurations specifying non existing keys as required', () =>
-      fc.assert(
-        fc.property(fc.set(keyArb, { minLength: 1 }), keyArb, (keys, requiredKey) => {
-          fc.pre(!keys.includes(requiredKey));
-
-          const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-          for (const k of keys) {
-            recordModel[k] = constant(k);
-          }
-
-          expect(() =>
-            record(recordModel, {
-              requiredKeys: [requiredKey],
-            })
-          ).toThrowError();
-        })
-      ));
-    it('Should reject configurations specifying both requiredKeys and withDeletedKeys (even undefined)', () =>
-      fc.assert(
-        fc.property(
-          fc.set(fc.record({ name: keyArb, required: fc.boolean() }), {
-            minLength: 1,
-            compare: (a, b) => a.name === b.name,
-          }),
-          fc.option(fc.constant(true), { nil: undefined }),
-          fc.option(fc.boolean(), { nil: undefined }),
-          (keys, withRequiredKeys, withDeletedKeys) => {
-            const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-            for (const k of keys) {
-              recordModel[k.name] = constant(k.name);
-            }
-
-            expect(() =>
-              record(recordModel, {
-                requiredKeys: withRequiredKeys ? keys.filter((k) => k.required).map((k) => k.name) : undefined,
-                withDeletedKeys: withDeletedKeys,
-              })
-            ).toThrowError();
-          }
-        )
-      ));
-    it('Should accept empty keys configurations with empty requiredKeys', () => {
-      const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-      expect(() => record(recordModel, { requiredKeys: [] })).not.toThrowError();
-    });
-    it.each`
-      keyName
-      ${'constructor'}
-      ${'toString'}
-      ${'__proto__'}
-    `('Should reject configurations specifying non own properties ($keyName) as requiredKeys', ({ keyName }) => {
-      const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-      expect(() => record(recordModel, { requiredKeys: [keyName] })).toThrowError();
-    });
-    it('Should reject configurations specifying non enumerable properties as requiredKeys', () => {
-      const keyName = 'k';
-      const recordModel: Record<string | symbol, Arbitrary<any>> = {};
-      Object.defineProperty(recordModel, keyName, { value: fc.boolean(), enumerable: false });
-      expect(() => record(recordModel, { requiredKeys: [keyName] })).toThrowError();
-    });
-
-    type Meta = { key: any; valueStart: number; kept: boolean };
-    const metaArbitrary = fc.set(
-      fc.record({
-        key: keyArb,
-        valueStart: fc.nat(1000),
-        kept: fc.boolean(),
-      }),
-      { compare: (v1, v2) => v1.key === v2.key }
-    );
-    const constraintsArbitrary = fc.oneof(
-      fc.record({ withDeletedKeys: fc.boolean() }, { requiredKeys: [] }),
-      fc.record({ withRequiredKeys: fc.constant<true>(true) }, { requiredKeys: [] })
-    );
-
-    describe('Given a custom record configuration', () => {
-      genericHelper.isValidArbitrary(
-        ([metas, constraints]: [Meta[], RecordConstraints<any>]) => {
-          const recordModel: Record<string | symbol, Arbitrary<number>> = {};
-          for (const m of metas) {
-            recordModel[m.key] = integer(m.valueStart, m.valueStart + 10);
-          }
-          return record(recordModel, constraints);
-        },
-        {
-          seedGenerator: fc
-            .tuple(metaArbitrary, constraintsArbitrary)
-            .map(([metas, constraintsMeta]: [Meta[], { withDeletedKeys?: boolean; withRequiredKeys?: true }]) => {
-              if ('withRequiredKeys' in constraintsMeta) {
-                return [metas, { requiredKeys: metas.filter((m) => m.kept).map((m) => m.key) }] as [
-                  Meta[],
-                  RecordConstraints
-                ];
-              }
-              return [metas, constraintsMeta] as [Meta[], RecordConstraints];
-            }),
-          isValidValue: (r: { [key: string]: number }, [metas, constraints]: [Meta[], RecordConstraints]) => {
-            // Rq: getOwnPropertyNames will also get non enumerable properties, but there are none in our case
-            for (const k of [...Object.getOwnPropertyNames(r), ...Object.getOwnPropertySymbols(r)]) {
-              // generated object should not have more keys
-              if (metas.findIndex((m) => m.key === k) === -1) return false;
-            }
-            for (const m of metas) {
-              // optional keys can be missing in the generated instance
-              if (
-                'withDeletedKeys' in constraints &&
-                constraints.withDeletedKeys === true &&
-                !Object.prototype.hasOwnProperty.call(r, m.key)
-              ) {
-                continue;
-              }
-              if (
-                'requiredKeys' in constraints &&
-                constraints.requiredKeys !== undefined &&
-                !constraints.requiredKeys.includes(m.key) &&
-                !Object.prototype.hasOwnProperty.call(r, m.key)
-              ) {
-                continue;
-              }
-              // values are associated to the right key (if key required)
-              if (typeof r[m.key] !== 'number') return false;
-              if (r[m.key] < m.valueStart) return false;
-              if (r[m.key] > m.valueStart + 10) return false;
-            }
-            return true;
-          },
-        }
-      );
-    });
-  });
-});
diff --git a/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts b/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
index 983f5050..f8855c86 100644
--- a/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
@@ -133,7 +133,7 @@ describe('BigIntArbitrary', () => {
       ));
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should always tells it can generate values included in the requested range', () =>
       fc.assert(
         fc.property(fc.bigInt(), fc.bigInt(), fc.bigInt(), (a, b, c) => {
@@ -142,7 +142,7 @@ describe('BigIntArbitrary', () => {
 
           // Act
           const arb = new BigIntArbitrary(min, max);
-          const out = arb.canGenerate(mid);
+          const out = arb.canShrinkWithoutContext(mid);
 
           // Assert
           expect(out).toBe(true);
@@ -164,7 +164,7 @@ describe('BigIntArbitrary', () => {
 
             // Act
             const arb = new BigIntArbitrary(min, max);
-            const out = arb.canGenerate(requested);
+            const out = arb.canShrinkWithoutContext(requested);
 
             // Assert
             expect(out).toBe(false);
@@ -189,7 +189,7 @@ describe('BigIntArbitrary', () => {
 
       // Act
       const arb = new BigIntArbitrary(min, max);
-      const out = arb.canGenerate(requested);
+      const out = arb.canShrinkWithoutContext(requested);
 
       // Assert
       expect(out).toBe(false);
diff --git a/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts b/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
index 9fc489c5..4c944dbc 100644
--- a/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
@@ -66,57 +66,57 @@ describe('CloneArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should return false if passed value does not have the right length', () =>
       fc.assert(
         fc.property(fc.nat({ max: 1000 }), fc.nat({ max: 1000 }), (numValues, numRequestedValues) => {
           // Arrange
           fc.pre(numValues !== numRequestedValues);
-          const { instance: sourceArb, canGenerate } = fakeNextArbitrary();
+          const { instance: sourceArb, canShrinkWithoutContext } = fakeNextArbitrary();
 
           // Act
           const arb = new CloneArbitrary(sourceArb, numValues);
-          const out = arb.canGenerate([...Array(numRequestedValues)]);
+          const out = arb.canShrinkWithoutContext([...Array(numRequestedValues)]);
 
           // Assert
           expect(out).toBe(false);
-          expect(canGenerate).not.toHaveBeenCalled();
+          expect(canShrinkWithoutContext).not.toHaveBeenCalled();
         })
       ));
 
     it('should return false if values are not equal regarding Object.is', () => {
       // Arrange
-      const { instance: sourceArb, canGenerate } = fakeNextArbitrary();
+      const { instance: sourceArb, canShrinkWithoutContext } = fakeNextArbitrary();
 
       // Act
       const arb = new CloneArbitrary(sourceArb, 2);
-      const out = arb.canGenerate([{}, {}]);
+      const out = arb.canShrinkWithoutContext([{}, {}]);
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it.each`
-      canGenerateValue
+      canShrinkWithoutContextValue
       ${true}
       ${false}
     `(
       'should ask sub-arbitrary whenever length is correct and children are equal regarding Object.is',
-      ({ canGenerateValue }) => {
+      ({ canShrinkWithoutContextValue }) => {
         // Arrange
         const value = {};
-        const { instance: sourceArb, canGenerate } = fakeNextArbitrary();
-        canGenerate.mockReturnValue(canGenerateValue);
+        const { instance: sourceArb, canShrinkWithoutContext } = fakeNextArbitrary();
+        canShrinkWithoutContext.mockReturnValue(canShrinkWithoutContextValue);
 
         // Act
         const arb = new CloneArbitrary(sourceArb, 2);
-        const out = arb.canGenerate([value, value]);
+        const out = arb.canShrinkWithoutContext([value, value]);
 
         // Assert
-        expect(out).toBe(canGenerateValue);
-        expect(canGenerate).toHaveBeenCalledTimes(1);
-        expect(canGenerate).toHaveBeenCalledWith(value);
+        expect(out).toBe(canShrinkWithoutContextValue);
+        expect(canShrinkWithoutContext).toHaveBeenCalledTimes(1);
+        expect(canShrinkWithoutContext).toHaveBeenCalledWith(value);
       }
     );
   });
@@ -244,7 +244,7 @@ class FirstArbitrary extends NextArbitrary<number> {
   generate(_mrng: Random): NextValue<number> {
     return new NextValue(expectedFirst, { step: 2 });
   }
-  canGenerate(_value: unknown): _value is number {
+  canShrinkWithoutContext(_value: unknown): _value is number {
     throw new Error('No call expected in that scenario');
   }
   shrink(value: number, context?: unknown): Stream<NextValue<number>> {
@@ -270,7 +270,7 @@ class CloneableArbitrary extends NextArbitrary<number> {
   generate(_mrng: Random): NextValue<number> {
     return new NextValue(this.instance(), { shrunkOnce: false });
   }
-  canGenerate(_value: unknown): _value is number {
+  canShrinkWithoutContext(_value: unknown): _value is number {
     throw new Error('No call expected in that scenario');
   }
   shrink(value: number, context?: unknown): Stream<NextValue<number>> {
diff --git a/test/unit/arbitrary/_internals/ConstantArbitrary.spec.ts b/test/unit/arbitrary/_internals/ConstantArbitrary.spec.ts
index 4156b22b..96829d00 100644
--- a/test/unit/arbitrary/_internals/ConstantArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/ConstantArbitrary.spec.ts
@@ -112,8 +112,8 @@ describe('ConstantArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
-    it("should mark value as 'canGenerate' whenever one of the original values is equal regarding Object.is", () =>
+  describe('canShrinkWithoutContext', () => {
+    it("should mark value as 'canShrinkWithoutContext' whenever one of the original values is equal regarding Object.is", () =>
       fc.assert(
         fc.property(fc.array(fc.anything(), { minLength: 1 }), fc.nat(), (values, mod) => {
           // Arrange
@@ -121,7 +121,7 @@ describe('ConstantArbitrary', () => {
 
           // Act
           const arb = new ConstantArbitrary(values);
-          const out = arb.canGenerate(selectedValue);
+          const out = arb.canShrinkWithoutContext(selectedValue);
 
           // Assert
           expect(out).toBe(true);
@@ -135,7 +135,7 @@ describe('ConstantArbitrary', () => {
 
       // Act
       const arb = new ConstantArbitrary<unknown>(values);
-      const out = arb.canGenerate(selectedValue);
+      const out = arb.canShrinkWithoutContext(selectedValue);
 
       // Assert
       expect(out).toBe(false); // Object.is([], []) is falsy
diff --git a/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts b/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
index a53553cd..03e0e942 100644
--- a/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
@@ -385,7 +385,7 @@ describe('FrequencyArbitrary', () => {
       ));
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should tell it cannot generate the value if no sub-arbitrary can generate the value', () =>
       fc.assert(
         fc.property(frequencyValidInputsArb, frequencyConstraintsArbFor({}), (validInputs, constraints) => {
@@ -396,14 +396,14 @@ describe('FrequencyArbitrary', () => {
           getDepthContextFor.mockReturnValue(depthContext);
           const value = Symbol();
           for (const input of warbs) {
-            input.arbitraryMeta.canGenerate.mockReturnValue(false);
+            input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(false);
           }
 
           // Act
           const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
 
           // Assert
-          expect(arb.canGenerate(value)).toBe(false);
+          expect(arb.canShrinkWithoutContext(value)).toBe(false);
         })
       ));
 
@@ -426,15 +426,15 @@ describe('FrequencyArbitrary', () => {
             getDepthContextFor.mockReturnValue(depthContext);
             const value = Symbol();
             for (const input of warbs) {
-              input.arbitraryMeta.canGenerate.mockReturnValue(false);
+              input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(false);
             }
-            warbs[position % validInputs.length].arbitraryMeta.canGenerate.mockReturnValue(true);
+            warbs[position % validInputs.length].arbitraryMeta.canShrinkWithoutContext.mockReturnValue(true);
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
 
             // Assert
-            expect(arb.canGenerate(value)).toBe(false);
+            expect(arb.canShrinkWithoutContext(value)).toBe(false);
           }
         )
       ));
@@ -457,15 +457,15 @@ describe('FrequencyArbitrary', () => {
             for (let index = 0; index !== warbs.length; ++index) {
               const input = warbs[index];
               const can = index === selectedIndex;
-              input.arbitraryMeta.canGenerate.mockReturnValue(can);
+              input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);
             }
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
 
             // Assert
-            expect(arb.canGenerate(value)).toBe(true);
-            expect(warbs[selectedIndex].arbitraryMeta.canGenerate).toHaveBeenCalledWith(value);
+            expect(arb.canShrinkWithoutContext(value)).toBe(true);
+            expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
           }
         )
       ));
@@ -487,17 +487,19 @@ describe('FrequencyArbitrary', () => {
             getDepthContextFor.mockReturnValue(depthContext);
             const value = Symbol();
             for (let index = 0; index !== warbs.length; ++index) {
-              warbs[index].arbitraryMeta.canGenerate.mockReturnValue(index === 0 ? firstCanOrNot : allOthersCanOrNot);
+              warbs[index].arbitraryMeta.canShrinkWithoutContext.mockReturnValue(
+                index === 0 ? firstCanOrNot : allOthersCanOrNot
+              );
             }
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
 
             // Assert
-            expect(arb.canGenerate(value)).toBe(firstCanOrNot);
-            expect(warbs[0].arbitraryMeta.canGenerate).toHaveBeenCalledWith(value);
+            expect(arb.canShrinkWithoutContext(value)).toBe(firstCanOrNot);
+            expect(warbs[0].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
             for (let index = 1; index < warbs.length; ++index) {
-              expect(warbs[index].arbitraryMeta.canGenerate).not.toHaveBeenCalled();
+              expect(warbs[index].arbitraryMeta.canShrinkWithoutContext).not.toHaveBeenCalled();
             }
           }
         )
@@ -632,7 +634,7 @@ describe('FrequencyArbitrary', () => {
             for (let index = 0; index !== warbs.length; ++index) {
               const input = warbs[index];
               const can = index === selectedIndex;
-              input.arbitraryMeta.canGenerate.mockReturnValue(can);
+              input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);
               input.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(42), new NextValue(index)));
             }
 
@@ -642,7 +644,7 @@ describe('FrequencyArbitrary', () => {
 
             // Assert
             expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);
-            expect(warbs[selectedIndex].arbitraryMeta.canGenerate).toHaveBeenCalledWith(value);
+            expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
             expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value);
           }
         )
@@ -667,7 +669,7 @@ describe('FrequencyArbitrary', () => {
             for (let index = 0; index !== warbs.length; ++index) {
               const input = warbs[index];
               const can = index === selectedIndex;
-              input.arbitraryMeta.canGenerate.mockReturnValue(can);
+              input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);
               input.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(42), new NextValue(index)));
             }
             warbs[0].fallbackValue = { default: 48 };
@@ -686,7 +688,7 @@ describe('FrequencyArbitrary', () => {
             } else {
               expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);
             }
-            expect(warbs[selectedIndex].arbitraryMeta.canGenerate).toHaveBeenCalledWith(value);
+            expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
             expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value);
           }
         )
diff --git a/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts b/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
index d77ffdd8..9fe0f58d 100644
--- a/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
@@ -132,7 +132,7 @@ describe('IntegerArbitrary', () => {
       ));
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should always tells it can generate values included in the requested range', () =>
       fc.assert(
         fc.property(fc.maxSafeInteger(), fc.maxSafeInteger(), fc.maxSafeInteger(), (a, b, c) => {
@@ -141,7 +141,7 @@ describe('IntegerArbitrary', () => {
 
           // Act
           const arb = new IntegerArbitrary(min, max);
-          const out = arb.canGenerate(mid);
+          const out = arb.canShrinkWithoutContext(mid);
 
           // Assert
           expect(out).toBe(true);
@@ -163,7 +163,7 @@ describe('IntegerArbitrary', () => {
 
             // Act
             const arb = new IntegerArbitrary(min, max);
-            const out = arb.canGenerate(requested);
+            const out = arb.canShrinkWithoutContext(requested);
 
             // Assert
             expect(out).toBe(false);
@@ -186,7 +186,7 @@ describe('IntegerArbitrary', () => {
 
       // Act
       const arb = new IntegerArbitrary(min, max);
-      const out = arb.canGenerate(requested);
+      const out = arb.canShrinkWithoutContext(requested);
 
       // Assert
       expect(out).toBe(false);
diff --git a/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
index 75a2817a..c3d66a8a 100644
--- a/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
@@ -34,33 +34,33 @@ describe('LazyArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it.each`
       returnValue
       ${false}
       ${true}
-    `('should call underlying on canGenerate ($returnValue)', ({ returnValue }) => {
+    `('should call underlying on canShrinkWithoutContext ($returnValue)', ({ returnValue }) => {
       // Arrange
       const value = Symbol();
-      const { instance: underlying, canGenerate } = fakeNextArbitrary();
-      canGenerate.mockReturnValue(returnValue);
+      const { instance: underlying, canShrinkWithoutContext } = fakeNextArbitrary();
+      canShrinkWithoutContext.mockReturnValue(returnValue);
       const lazy = new LazyArbitrary('id007');
       lazy.underlying = underlying;
 
       // Act
-      const out = lazy.canGenerate(value);
+      const out = lazy.canShrinkWithoutContext(value);
 
       // Assert
       expect(out).toBe(returnValue);
-      expect(canGenerate).toHaveBeenCalledWith(value);
+      expect(canShrinkWithoutContext).toHaveBeenCalledWith(value);
     });
 
-    it('should fail to check canGenerate when no underlying arbitrary', () => {
+    it('should fail to check canShrinkWithoutContext when no underlying arbitrary', () => {
       // Arrange / Act
       const lazy = new LazyArbitrary('id007');
 
       // Assert
-      expect(() => lazy.canGenerate(1)).toThrowErrorMatchingInlineSnapshot(
+      expect(() => lazy.canShrinkWithoutContext(1)).toThrowErrorMatchingInlineSnapshot(
         `"Lazy arbitrary \\"id007\\" not correctly initialized"`
       );
     });
diff --git a/test/unit/arbitrary/_internals/SchedulerArbitrary.spec.ts b/test/unit/arbitrary/_internals/SchedulerArbitrary.spec.ts
index ef7605f6..8522bb92 100644
--- a/test/unit/arbitrary/_internals/SchedulerArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/SchedulerArbitrary.spec.ts
@@ -106,14 +106,14 @@ describe('SchedulerArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should return false for any Scheduler received without any context (even for SchedulerImplem)', () => {
       // Arrange
       const act = jest.fn();
 
       // Act
       const arb = new SchedulerArbitrary(act);
-      const out = arb.canGenerate(
+      const out = arb.canShrinkWithoutContext(
         new SchedulerImplemMock.SchedulerImplem(act, { clone: jest.fn(), nextTaskIndex: jest.fn() })
       );
 
@@ -133,7 +133,7 @@ describe('SchedulerArbitrary', () => {
       // Act
       const arb = new SchedulerArbitrary(act);
       const g = arb.generate(mrng, undefined);
-      const out = arb.canGenerate(g.value);
+      const out = arb.canShrinkWithoutContext(g.value);
 
       // Assert
       expect(out).toBe(false);
diff --git a/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts b/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
index c4083643..1a4f4cbb 100644
--- a/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
@@ -206,7 +206,7 @@ describe('StreamArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     function* infiniteG() {
       yield 1;
     }
@@ -217,30 +217,30 @@ describe('StreamArbitrary', () => {
       ${new Stream(infiniteG())}   | ${'infinite stream'}
     `('should return false for any Stream whatever the size ($description)', ({ data }) => {
       // Arrange
-      const { instance: sourceArb, canGenerate } = fakeNextArbitrary();
+      const { instance: sourceArb, canShrinkWithoutContext } = fakeNextArbitrary();
 
       // Act
       const arb = new StreamArbitrary(sourceArb);
-      const out = arb.canGenerate(data);
+      const out = arb.canShrinkWithoutContext(data);
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it('should return false even for its own values', () => {
       // Arrange
-      const { instance: sourceArb, canGenerate } = fakeNextArbitrary();
+      const { instance: sourceArb, canShrinkWithoutContext } = fakeNextArbitrary();
       const { instance: mrng } = fakeRandom();
 
       // Act
       const arb = new StreamArbitrary(sourceArb);
       const g = arb.generate(mrng, undefined);
-      const out = arb.canGenerate(g.value);
+      const out = arb.canShrinkWithoutContext(g.value);
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
   });
 
@@ -273,7 +273,7 @@ describe('StreamArbitrary (integration)', () => {
   };
 
   const isCorrect = (value: Stream<number>) =>
-    value instanceof Stream && [...value.take(10)].every((v) => sourceArb.canGenerate(v));
+    value instanceof Stream && [...value.take(10)].every((v) => sourceArb.canShrinkWithoutContext(v));
 
   const streamBuilder = () => new StreamArbitrary(sourceArb);
 
diff --git a/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts b/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
index b25794e7..8c1af361 100644
--- a/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
@@ -96,7 +96,7 @@ describe('TupleArbitrary', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it.each`
       canA     | canB     | canC
       ${false} | ${false} | ${false}
@@ -105,48 +105,48 @@ describe('TupleArbitrary', () => {
       ${true}  | ${true}  | ${false}
       ${true}  | ${true}  | ${true}
     `(
-      'should merge results coming from underlyings for canGenerate if received array has the right size',
+      'should merge results coming from underlyings for canShrinkWithoutContext if received array has the right size',
       ({ canA, canB, canC }) => {
         // Arrange
         const vA = Symbol();
         const vB = Symbol();
         const vC = Symbol();
-        const { instance: instanceA, canGenerate: canGenerateA } = fakeNextArbitrary<symbol>();
-        const { instance: instanceB, canGenerate: canGenerateB } = fakeNextArbitrary<symbol>();
-        const { instance: instanceC, canGenerate: canGenerateC } = fakeNextArbitrary<symbol>();
-        canGenerateA.mockReturnValueOnce(canA);
-        canGenerateB.mockReturnValueOnce(canB);
-        canGenerateC.mockReturnValueOnce(canC);
+        const { instance: instanceA, canShrinkWithoutContext: canShrinkWithoutContextA } = fakeNextArbitrary<symbol>();
+        const { instance: instanceB, canShrinkWithoutContext: canShrinkWithoutContextB } = fakeNextArbitrary<symbol>();
+        const { instance: instanceC, canShrinkWithoutContext: canShrinkWithoutContextC } = fakeNextArbitrary<symbol>();
+        canShrinkWithoutContextA.mockReturnValueOnce(canA);
+        canShrinkWithoutContextB.mockReturnValueOnce(canB);
+        canShrinkWithoutContextC.mockReturnValueOnce(canC);
 
         // Act
         const arb = new TupleArbitrary([instanceA, instanceB, instanceC]);
-        const out = arb.canGenerate([vA, vB, vC]);
+        const out = arb.canShrinkWithoutContext([vA, vB, vC]);
 
         // Assert
         expect(out).toBe(canA && canB && canC);
-        expect(canGenerateA).toHaveBeenCalledWith(vA);
-        if (canA) expect(canGenerateB).toHaveBeenCalledWith(vB);
-        else expect(canGenerateB).not.toHaveBeenCalled();
-        if (canA && canB) expect(canGenerateC).toHaveBeenCalledWith(vC);
-        else expect(canGenerateC).not.toHaveBeenCalled();
+        expect(canShrinkWithoutContextA).toHaveBeenCalledWith(vA);
+        if (canA) expect(canShrinkWithoutContextB).toHaveBeenCalledWith(vB);
+        else expect(canShrinkWithoutContextB).not.toHaveBeenCalled();
+        if (canA && canB) expect(canShrinkWithoutContextC).toHaveBeenCalledWith(vC);
+        else expect(canShrinkWithoutContextC).not.toHaveBeenCalled();
       }
     );
 
-    it('should not call underlyings on canGenerate if size is invalid', () => {
+    it('should not call underlyings on canShrinkWithoutContext if size is invalid', () => {
       // Arrange
-      const { instance: instanceA, canGenerate: canGenerateA } = fakeNextArbitrary<symbol>();
-      const { instance: instanceB, canGenerate: canGenerateB } = fakeNextArbitrary<symbol>();
-      const { instance: instanceC, canGenerate: canGenerateC } = fakeNextArbitrary<symbol>();
+      const { instance: instanceA, canShrinkWithoutContext: canShrinkWithoutContextA } = fakeNextArbitrary<symbol>();
+      const { instance: instanceB, canShrinkWithoutContext: canShrinkWithoutContextB } = fakeNextArbitrary<symbol>();
+      const { instance: instanceC, canShrinkWithoutContext: canShrinkWithoutContextC } = fakeNextArbitrary<symbol>();
 
       // Act
       const arb = new TupleArbitrary([instanceA, instanceB, instanceC]);
-      const out = arb.canGenerate([Symbol(), Symbol(), Symbol(), Symbol()]);
+      const out = arb.canShrinkWithoutContext([Symbol(), Symbol(), Symbol(), Symbol()]);
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerateA).not.toHaveBeenCalled();
-      expect(canGenerateB).not.toHaveBeenCalled();
-      expect(canGenerateC).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContextA).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContextB).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContextC).not.toHaveBeenCalled();
     });
   });
 
@@ -393,7 +393,7 @@ class FirstArbitrary extends NextArbitrary<number> {
   generate(_mrng: Random): NextValue<number> {
     return new NextValue(expectedFirst, { step: 2 });
   }
-  canGenerate(_value: unknown): _value is number {
+  canShrinkWithoutContext(_value: unknown): _value is number {
     throw new Error('No call expected in that scenario');
   }
   shrink(value: number, context?: unknown): Stream<NextValue<number>> {
@@ -415,7 +415,7 @@ class SecondArbitrary extends NextArbitrary<number> {
   generate(_mrng: Random): NextValue<number> {
     return new NextValue(expectedSecond, { step: 2 });
   }
-  canGenerate(_value: unknown): _value is number {
+  canShrinkWithoutContext(_value: unknown): _value is number {
     throw new Error('No call expected in that scenario');
   }
   shrink(value: number, context?: unknown): Stream<NextValue<number>> {
@@ -441,7 +441,7 @@ class CloneableArbitrary extends NextArbitrary<number> {
   generate(_mrng: Random): NextValue<number> {
     return new NextValue(this.instance(), { shrunkOnce: false });
   }
-  canGenerate(_value: unknown): _value is number {
+  canShrinkWithoutContext(_value: unknown): _value is number {
     throw new Error('No call expected in that scenario');
   }
   shrink(value: number, context?: unknown): Stream<NextValue<number>> {
diff --git a/test/unit/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.spec.ts b/test/unit/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.spec.ts
new file mode 100644
index 00000000..04a82895
--- /dev/null
+++ b/test/unit/arbitrary/_internals/builders/PartialRecordArbitraryBuilder.spec.ts
@@ -0,0 +1,168 @@
+import { fakeNextArbitrary } from '../../../check/arbitrary/generic/NextArbitraryHelpers';
+import { convertFromNext, convertToNext } from '../../../../../src/check/arbitrary/definition/Converters';
+import { buildPartialRecordArbitrary } from '../../../../../src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder';
+
+import * as OptionMock from '../../../../../src/arbitrary/option';
+import * as TupleMock from '../../../../../src/arbitrary/tuple';
+import * as ValuesAndSeparateKeysToObjectMock from '../../../../../src/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject';
+
+function beforeEachHook() {
+  jest.resetModules();
+  jest.restoreAllMocks();
+}
+beforeEach(beforeEachHook);
+
+describe('buildPartialRecordArbitrary', () => {
+  it('should never wrap arbitraries linked to required keys and forward all keys to mappers', () => {
+    // Arrange
+    const { instance: mappedInstance } = fakeNextArbitrary<any>();
+    const { instance: tupleInstance, map } = fakeNextArbitrary<any[]>();
+    const option = jest.spyOn(OptionMock, 'option');
+    const tuple = jest.spyOn(TupleMock, 'tuple');
+    tuple.mockReturnValue(convertFromNext(tupleInstance));
+    map.mockReturnValue(mappedInstance);
+
+    const mapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectMapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectMapper'
+    );
+    buildValuesAndSeparateKeysToObjectMapper.mockReturnValue(mapper);
+
+    const unmapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectUnmapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectUnmapper'
+    );
+    buildValuesAndSeparateKeysToObjectUnmapper.mockReturnValue(unmapper);
+
+    const arbKey1 = fakeNextArbitrary();
+    const arbKey2 = fakeNextArbitrary();
+    const recordModel = {
+      a: convertFromNext(arbKey1),
+      b: convertFromNext(arbKey2),
+    };
+    const requiredKeys: (keyof typeof recordModel)[] = ['a', 'b'];
+    const allKeys: (keyof typeof recordModel)[] = ['a', 'b'];
+
+    // Act
+    const arb = buildPartialRecordArbitrary(recordModel, requiredKeys);
+
+    // Assert
+    expect(convertToNext(arb)).toBe(mappedInstance);
+    expect(option).not.toHaveBeenCalled();
+    expect(tuple).toHaveBeenCalledTimes(1);
+    expect(tuple).toHaveBeenCalledWith(recordModel.a, recordModel.b);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(map).toHaveBeenCalledTimes(1);
+    expect(map).toHaveBeenCalledWith(mapper, unmapper);
+  });
+
+  it('should wrap arbitraries not linked to required keys into option and forward all keys to mappers', () => {
+    // Arrange
+    const { instance: mappedInstance } = fakeNextArbitrary<any>();
+    const { instance: tupleInstance, map } = fakeNextArbitrary<any[]>();
+    const { instance: optionInstance1 } = fakeNextArbitrary();
+    const { instance: optionInstance2 } = fakeNextArbitrary();
+    const option = jest.spyOn(OptionMock, 'option');
+    const tuple = jest.spyOn(TupleMock, 'tuple');
+    const optionInstance1Old = convertFromNext(optionInstance1);
+    const optionInstance2Old = convertFromNext(optionInstance2);
+    option.mockReturnValueOnce(optionInstance1Old).mockReturnValueOnce(optionInstance2Old);
+    tuple.mockReturnValue(convertFromNext(tupleInstance));
+    map.mockReturnValue(mappedInstance);
+
+    const mapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectMapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectMapper'
+    );
+    buildValuesAndSeparateKeysToObjectMapper.mockReturnValue(mapper);
+
+    const unmapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectUnmapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectUnmapper'
+    );
+    buildValuesAndSeparateKeysToObjectUnmapper.mockReturnValue(unmapper);
+
+    const arbKey1 = fakeNextArbitrary();
+    const arbKey2 = fakeNextArbitrary();
+    const arbKey3 = fakeNextArbitrary();
+    const recordModel = {
+      a: convertFromNext(arbKey1),
+      b: convertFromNext(arbKey2),
+      c: convertFromNext(arbKey3),
+    };
+    const requiredKeys: (keyof typeof recordModel)[] = ['b'];
+    const allKeys: (keyof typeof recordModel)[] = ['a', 'b', 'c'];
+
+    // Act
+    const arb = buildPartialRecordArbitrary(recordModel, requiredKeys);
+
+    // Assert
+    expect(convertToNext(arb)).toBe(mappedInstance);
+    expect(option).toHaveBeenCalledTimes(2);
+    expect(option).toHaveBeenCalledWith(recordModel.a, { nil: expect.any(Symbol) });
+    expect(option).toHaveBeenCalledWith(recordModel.c, { nil: expect.any(Symbol) });
+    expect(tuple).toHaveBeenCalledTimes(1);
+    expect(tuple).toHaveBeenCalledWith(optionInstance1Old, recordModel.b, optionInstance2Old);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(map).toHaveBeenCalledTimes(1);
+    expect(map).toHaveBeenCalledWith(mapper, unmapper);
+  });
+
+  it('should not wrap any arbitrary when required keys is not specified (all required) and forward all keys to mappers', () => {
+    // Arrange
+    const { instance: mappedInstance } = fakeNextArbitrary<any>();
+    const { instance: tupleInstance, map } = fakeNextArbitrary<any[]>();
+    const option = jest.spyOn(OptionMock, 'option');
+    const tuple = jest.spyOn(TupleMock, 'tuple');
+    tuple.mockReturnValue(convertFromNext(tupleInstance));
+    map.mockReturnValue(mappedInstance);
+
+    const mapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectMapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectMapper'
+    );
+    buildValuesAndSeparateKeysToObjectMapper.mockReturnValue(mapper);
+
+    const unmapper = jest.fn();
+    const buildValuesAndSeparateKeysToObjectUnmapper = jest.spyOn(
+      ValuesAndSeparateKeysToObjectMock,
+      'buildValuesAndSeparateKeysToObjectUnmapper'
+    );
+    buildValuesAndSeparateKeysToObjectUnmapper.mockReturnValue(unmapper);
+
+    const arbKey1 = fakeNextArbitrary();
+    const arbKey2 = fakeNextArbitrary();
+    const recordModel = {
+      a: convertFromNext(arbKey1),
+      b: convertFromNext(arbKey2),
+    };
+    const requiredKeys = undefined;
+    const allKeys: (keyof typeof recordModel)[] = ['a', 'b'];
+
+    // Act
+    const arb = buildPartialRecordArbitrary(recordModel, requiredKeys);
+
+    // Assert
+    expect(convertToNext(arb)).toBe(mappedInstance);
+    expect(option).not.toHaveBeenCalled();
+    expect(tuple).toHaveBeenCalledTimes(1);
+    expect(tuple).toHaveBeenCalledWith(recordModel.a, recordModel.b);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectMapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledTimes(1);
+    expect(buildValuesAndSeparateKeysToObjectUnmapper).toHaveBeenCalledWith(allKeys, expect.any(Symbol));
+    expect(map).toHaveBeenCalledTimes(1);
+    expect(map).toHaveBeenCalledWith(mapper, unmapper);
+  });
+});
diff --git a/test/unit/arbitrary/_internals/mappers/PatternsToString.spec.ts b/test/unit/arbitrary/_internals/mappers/PatternsToString.spec.ts
index 6617269a..e39217d9 100644
--- a/test/unit/arbitrary/_internals/mappers/PatternsToString.spec.ts
+++ b/test/unit/arbitrary/_internals/mappers/PatternsToString.spec.ts
@@ -28,8 +28,8 @@ describe('patternsToStringUnmapperFor', () => {
     ({ sourceChunks, source, constraints, expectedChunks }) => {
       // Arrange
       const sourceChunksSet = new Set(sourceChunks);
-      const { instance, canGenerate } = fakeNextArbitrary<string>();
-      canGenerate.mockImplementation((value) => sourceChunksSet.has(value as string));
+      const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+      canShrinkWithoutContext.mockImplementation((value) => sourceChunksSet.has(value as string));
 
       // Act
       const unmapper = patternsToStringUnmapperFor(instance, constraints);
@@ -48,8 +48,8 @@ describe('patternsToStringUnmapperFor', () => {
   `('should throw when string cannot be split into chunks ($constraints)', ({ sourceChunks, source, constraints }) => {
     // Arrange
     const sourceChunksSet = new Set(sourceChunks);
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation((value) => sourceChunksSet.has(value as string));
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation((value) => sourceChunksSet.has(value as string));
 
     // Act / Assert
     const unmapper = patternsToStringUnmapperFor(instance, constraints);
@@ -66,8 +66,8 @@ describe('patternsToStringUnmapperFor', () => {
         (sourceChunks, sourceMods) => {
           // Arrange
           const sourceChunksSet = new Set(sourceChunks);
-          const { instance, canGenerate } = fakeNextArbitrary<string>();
-          canGenerate.mockImplementation((value) => sourceChunksSet.has(value as string));
+          const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+          canShrinkWithoutContext.mockImplementation((value) => sourceChunksSet.has(value as string));
           const source = sourceMods.map((mod) => sourceChunks[mod % sourceChunks.length]).join('');
 
           // Act
@@ -95,8 +95,8 @@ describe('patternsToStringUnmapperFor', () => {
         (sourceChunks, sourceMods, constraintsMinOffset, constraintsMaxOffset) => {
           // Arrange
           const sourceChunksSet = new Set(sourceChunks);
-          const { instance, canGenerate } = fakeNextArbitrary<string>();
-          canGenerate.mockImplementation((value) => sourceChunksSet.has(value as string));
+          const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+          canShrinkWithoutContext.mockImplementation((value) => sourceChunksSet.has(value as string));
           const source = sourceMods.map((mod) => sourceChunks[mod % sourceChunks.length]).join('');
           const constraints = {
             minLength: Math.max(0, sourceMods.length - constraintsMinOffset),
diff --git a/test/unit/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.spec.ts b/test/unit/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.spec.ts
new file mode 100644
index 00000000..27babb04
--- /dev/null
+++ b/test/unit/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.spec.ts
@@ -0,0 +1,130 @@
+import {
+  buildValuesAndSeparateKeysToObjectMapper,
+  buildValuesAndSeparateKeysToObjectUnmapper,
+} from '../../../../../src/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject';
+
+describe('buildValuesAndSeparateKeysToObjectMapper', () => {
+  it('should create instances with Object prototype', () => {
+    // Arrange
+    const keys: (string | symbol)[] = [];
+    const magicNoValue = Symbol('no-value');
+    const values: any[] = [];
+
+    // Act
+    const mapper = buildValuesAndSeparateKeysToObjectMapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const obj = mapper(values);
+
+    // Assert
+    expect(obj.constructor).toBe(Object);
+    expect(obj.__proto__).toBe(Object.prototype);
+  });
+
+  it('should replace magic values by no key', () => {
+    // Arrange
+    const keys: (string | symbol)[] = ['a', 'b', 'c', 'd'];
+    const magicNoValue = Symbol('no-value');
+    const values: any[] = [undefined, magicNoValue, null, 0];
+
+    // Act
+    const mapper = buildValuesAndSeparateKeysToObjectMapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const obj = mapper(values);
+
+    // Assert
+    expect(obj).toHaveProperty('a');
+    expect(obj).not.toHaveProperty('b');
+    expect(obj).toHaveProperty('c');
+    expect(obj).toHaveProperty('d');
+    expect(obj).toEqual({ a: undefined, c: null, d: 0 });
+  });
+});
+
+describe('buildValuesAndSeparateKeysToObjectUnmapper', () => {
+  it('should properly unmap basic instances of Object without keys', () => {
+    // Arrange
+    const obj = {};
+    const keys: (string | symbol)[] = [];
+    const magicNoValue = Symbol('no-value');
+
+    // Act
+    const unmapper = buildValuesAndSeparateKeysToObjectUnmapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const values = unmapper(obj);
+
+    // Assert
+    expect(values).toEqual([]);
+  });
+
+  it('should properly unmap basic instances of Object with multiple keys and no missing', () => {
+    // Arrange
+    const obj = { a: 'e', 1: 'hello', b: undefined };
+    const keys: (string | symbol)[] = ['b', '1', 'a'];
+    const magicNoValue = Symbol('no-value');
+
+    // Act
+    const unmapper = buildValuesAndSeparateKeysToObjectUnmapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const values = unmapper(obj);
+
+    // Assert
+    expect(values).toEqual([undefined, 'hello', 'e']);
+  });
+
+  it('should properly unmap instances of Object with known symbols as keys', () => {
+    // Arrange
+    const s1 = Symbol('s1');
+    const s2 = Symbol('s2');
+    const obj = { a: 'e', [s2]: 'hello', [s1]: undefined };
+    const keys: (string | symbol)[] = [s2, s1, 'a'];
+    const magicNoValue = Symbol('no-value');
+
+    // Act
+    const unmapper = buildValuesAndSeparateKeysToObjectUnmapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const values = unmapper(obj);
+
+    // Assert
+    expect(values).toEqual(['hello', undefined, 'e']);
+  });
+
+  it('should properly unmap instances of Object with missing keys', () => {
+    // Arrange
+    const s1 = Symbol('s1');
+    const s2 = Symbol('s2');
+    const s3 = Symbol('s2');
+    const obj = { a: 'e', [s2]: 'hello', [s1]: undefined };
+    const keys: (string | symbol)[] = [s2, 'b', s1, 'a', 'd', s3];
+    const magicNoValue = Symbol('no-value');
+
+    // Act
+    const unmapper = buildValuesAndSeparateKeysToObjectUnmapper<any, typeof magicNoValue>(keys, magicNoValue);
+    const values = unmapper(obj);
+
+    // Assert
+    expect(values).toEqual(['hello', magicNoValue, undefined, 'e', magicNoValue, magicNoValue]);
+  });
+
+  it.each`
+    value                                                                | condition
+    ${Object.create(null)}                                               | ${'it has no prototype'}
+    ${new (class A {})()}                                                | ${'it is not just a simple object but a more complex type'}
+    ${[]}                                                                | ${'it is an Array'}
+    ${new Number(0)}                                                     | ${'it is a boxed-Number'}
+    ${0}                                                                 | ${'it is a number'}
+    ${null}                                                              | ${'it is null'}
+    ${undefined}                                                         | ${'it is undefined'}
+    ${{ [Symbol('unknown')]: 5 }}                                        | ${'it contains an unknown symbol property'}
+    ${{ [Symbol('unknown')]: 5, a: 6, [Symbol.for('a')]: 7 }}            | ${'it contains an unknown symbol property (even if others are there)'}
+    ${{ unknown: 5 }}                                                    | ${'it contains an unknown named property'}
+    ${{ unknown: 5, a: 6, [Symbol.for('a')]: 7 }}                        | ${'it contains an unknown named property (even if others are there)'}
+    ${Object.defineProperty({}, 'a', { value: 5, configurable: false })} | ${'it contains a non-configurable property'}
+    ${Object.defineProperty({}, 'a', { value: 5, enumerable: false })}   | ${'it contains a non-enumerable property'}
+    ${Object.defineProperty({}, 'a', { value: 5, writable: false })}     | ${'it contains a non-writable property'}
+    ${Object.defineProperty({}, 'a', { get: () => 5 })}                  | ${'it contains a get property'}
+    ${Object.defineProperty({}, 'a', { set: () => {} })}                 | ${'it contains a set property'}
+  `('should reject unmap on instance when $condition', ({ value }) => {
+    // Arrange
+    const keys: (string | symbol)[] = ['a', Symbol.for('a')];
+    const magicNoValue = Symbol('no-value');
+    const unmapper = buildValuesAndSeparateKeysToObjectUnmapper<any, typeof magicNoValue>(keys, magicNoValue);
+
+    // Act / Assert
+    expect(() => unmapper(value)).toThrowError();
+  });
+});
diff --git a/test/unit/arbitrary/_internals/mappers/WordsToLorem.spec.ts b/test/unit/arbitrary/_internals/mappers/WordsToLorem.spec.ts
index 045cc273..fc05cab3 100644
--- a/test/unit/arbitrary/_internals/mappers/WordsToLorem.spec.ts
+++ b/test/unit/arbitrary/_internals/mappers/WordsToLorem.spec.ts
@@ -39,8 +39,8 @@ describe('wordsToJoinedStringMapper', () => {
 describe('wordsToJoinedStringUnmapperFor', () => {
   it('should unmap string made of words strictly coming from the source', () => {
     // Arrange
-    const { instance: wordsArbitrary, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance: wordsArbitrary, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world', 'winter', 'summer'].includes(value)
     );
 
@@ -54,8 +54,8 @@ describe('wordsToJoinedStringUnmapperFor', () => {
 
   it('should unmap string made of words with some having trimmed commas', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello,', 'world,', 'winter', 'summer'].includes(value)
     );
 
@@ -69,8 +69,8 @@ describe('wordsToJoinedStringUnmapperFor', () => {
 
   it('should reject strings containing unknown words', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello,', 'world,', 'spring', 'summer'].includes(value)
     );
 
@@ -83,8 +83,8 @@ describe('wordsToJoinedStringUnmapperFor', () => {
     fc.assert(
       fc.property(wordsArrayArbitrary, (words) => {
         // Arrange
-        const { instance, canGenerate } = fakeNextArbitrary<string>();
-        canGenerate.mockImplementation((value) => typeof value === 'string' && words.includes(value));
+        const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+        canShrinkWithoutContext.mockImplementation((value) => typeof value === 'string' && words.includes(value));
 
         // Act
         const mapped = wordsToJoinedStringMapper(words);
@@ -117,8 +117,8 @@ describe('wordsToSentenceMapper', () => {
 describe('wordsToSentenceUnmapperFor', () => {
   it('should unmap string made of words strictly coming from the source', () => {
     // Arrange
-    const { instance: wordsArbitrary, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance: wordsArbitrary, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world', 'winter', 'summer'].includes(value)
     );
 
@@ -132,8 +132,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it('should unmap string made of words with last one having trimmed comma', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello,', 'world,', 'winter', 'summer'].includes(value)
     );
 
@@ -147,8 +147,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it('should reject strings containing unknown words', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world,', 'spring', 'summer'].includes(value)
     );
 
@@ -160,8 +160,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it('should reject strings not starting by a capital leter', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world,', 'winter', 'summer'].includes(value)
     );
 
@@ -173,8 +173,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it('should reject strings not ending by a point', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world,', 'winter', 'summer'].includes(value)
     );
 
@@ -186,8 +186,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it('should reject strings with last word ending by a comma followed by point', () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world,', 'winter', 'summer'].includes(value)
     );
 
@@ -199,8 +199,8 @@ describe('wordsToSentenceUnmapperFor', () => {
 
   it("should reject strings if one of first words do not includes it's expected comma", () => {
     // Arrange
-    const { instance, canGenerate } = fakeNextArbitrary<string>();
-    canGenerate.mockImplementation(
+    const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+    canShrinkWithoutContext.mockImplementation(
       (value) => typeof value === 'string' && ['hello', 'world,', 'winter,', 'summer'].includes(value)
     );
 
@@ -214,8 +214,8 @@ describe('wordsToSentenceUnmapperFor', () => {
     fc.assert(
       fc.property(wordsArrayArbitrary, (words) => {
         // Arrange
-        const { instance, canGenerate } = fakeNextArbitrary<string>();
-        canGenerate.mockImplementation((value) => typeof value === 'string' && words.includes(value));
+        const { instance, canShrinkWithoutContext } = fakeNextArbitrary<string>();
+        canShrinkWithoutContext.mockImplementation((value) => typeof value === 'string' && words.includes(value));
 
         // Act
         const mapped = wordsToSentenceMapper(words);
diff --git a/test/unit/arbitrary/dictionary.spec.ts b/test/unit/arbitrary/dictionary.spec.ts
index b03b20bb..a9820dab 100644
--- a/test/unit/arbitrary/dictionary.spec.ts
+++ b/test/unit/arbitrary/dictionary.spec.ts
@@ -63,7 +63,7 @@ class FromValuesArbitrary<T> extends NextArbitrary<T> {
     const index = mrng.nextInt(0, this.source.length - 1);
     return new NextValue(this.source[index]);
   }
-  canGenerate(value: unknown): value is T {
+  canShrinkWithoutContext(value: unknown): value is T {
     // includes might mix 0 and -0
     return this.source.includes(value as any);
   }
diff --git a/test/unit/arbitrary/fullUnicodeString.spec.ts b/test/unit/arbitrary/fullUnicodeString.spec.ts
index fd4ec907..2013533c 100644
--- a/test/unit/arbitrary/fullUnicodeString.spec.ts
+++ b/test/unit/arbitrary/fullUnicodeString.spec.ts
@@ -84,7 +84,7 @@ describe('fullUnicodeString (integration)', () => {
   `('should be able to generate $source with fc.fullUnicodeString()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(fullUnicodeString());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(true);
@@ -97,7 +97,7 @@ describe('fullUnicodeString (integration)', () => {
   `('should not be able to generate $source with fc.fullUnicodeString()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(fullUnicodeString());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(false);
diff --git a/test/unit/arbitrary/ipV4.spec.ts b/test/unit/arbitrary/ipV4.spec.ts
index 5532a2b1..a8ea9c5e 100644
--- a/test/unit/arbitrary/ipV4.spec.ts
+++ b/test/unit/arbitrary/ipV4.spec.ts
@@ -59,7 +59,7 @@ describe('ipV4 (integration)', () => {
   `('should be able to generate $source with fc.ipV4()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(ipV4());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(true);
@@ -74,7 +74,7 @@ describe('ipV4 (integration)', () => {
   `('should not be able to generate $source with fc.ipV4()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(ipV4());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(false);
diff --git a/test/unit/arbitrary/ipV4Extended.spec.ts b/test/unit/arbitrary/ipV4Extended.spec.ts
index a938f406..e9f10ae9 100644
--- a/test/unit/arbitrary/ipV4Extended.spec.ts
+++ b/test/unit/arbitrary/ipV4Extended.spec.ts
@@ -75,7 +75,7 @@ describe('ipV4Extended (integration)', () => {
   `('should be able to generate $source with fc.ipV4Extended()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(ipV4Extended());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(true);
@@ -93,7 +93,7 @@ describe('ipV4Extended (integration)', () => {
   `('should not be able to generate $source with fc.ipV4Extended()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(ipV4Extended());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(false);
diff --git a/test/unit/arbitrary/ipV6.spec.ts b/test/unit/arbitrary/ipV6.spec.ts
index 5515175f..cfbb91a4 100644
--- a/test/unit/arbitrary/ipV6.spec.ts
+++ b/test/unit/arbitrary/ipV6.spec.ts
@@ -151,7 +151,7 @@ describe('ipV6 (integration)', () => {
   `('should be able to generate $source with fc.ipV6()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(ipV6());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(true);
diff --git a/test/unit/arbitrary/letrec.spec.ts b/test/unit/arbitrary/letrec.spec.ts
index 9ed77a91..7fa6166a 100644
--- a/test/unit/arbitrary/letrec.spec.ts
+++ b/test/unit/arbitrary/letrec.spec.ts
@@ -261,16 +261,16 @@ describe('letrec', () => {
     });
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it.each`
       expectedStatus
       ${false}
       ${true}
-    `('should call canGenerate on the targets', ({ expectedStatus }) => {
+    `('should call canShrinkWithoutContext on the targets', ({ expectedStatus }) => {
       // Arrange
       const expectedValue = Symbol();
-      const { instance: simpleArb, canGenerate } = fakeNextArbitrary();
-      canGenerate.mockReturnValueOnce(expectedStatus);
+      const { instance: simpleArb, canShrinkWithoutContext } = fakeNextArbitrary();
+      canShrinkWithoutContext.mockReturnValueOnce(expectedStatus);
       const { arb1 } = letrec((tie) => {
         return {
           arb1: tie('arb2'),
@@ -279,15 +279,15 @@ describe('letrec', () => {
       });
 
       // Act
-      const out = arb1.canGenerate(expectedValue);
+      const out = arb1.canShrinkWithoutContext(expectedValue);
 
       // Assert
-      expect(canGenerate).toHaveBeenCalledTimes(1);
-      expect(canGenerate).toHaveBeenCalledWith(expectedValue);
+      expect(canShrinkWithoutContext).toHaveBeenCalledTimes(1);
+      expect(canShrinkWithoutContext).toHaveBeenCalledWith(expectedValue);
       expect(out).toBe(expectedStatus);
     });
 
-    it('should throw on canGenerate if tie receives an invalid parameter', () => {
+    it('should throw on canShrinkWithoutContext if tie receives an invalid parameter', () => {
       // Arrange
       const expectedValue = Symbol();
       const { arb1 } = letrec((tie) => ({
@@ -295,7 +295,7 @@ describe('letrec', () => {
       }));
 
       // Act / Assert
-      expect(() => arb1.canGenerate(expectedValue)).toThrowErrorMatchingInlineSnapshot(
+      expect(() => arb1.canShrinkWithoutContext(expectedValue)).toThrowErrorMatchingInlineSnapshot(
         `"Lazy arbitrary \\"missing\\" not correctly initialized"`
       );
     });
diff --git a/test/unit/arbitrary/record.spec.ts b/test/unit/arbitrary/record.spec.ts
new file mode 100644
index 00000000..db7430c9
--- /dev/null
+++ b/test/unit/arbitrary/record.spec.ts
@@ -0,0 +1,292 @@
+import fc from '../../../lib/fast-check';
+import { record, RecordConstraints } from '../../../src/arbitrary/record';
+import { Arbitrary } from '../../../src/check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { FakeIntegerArbitrary, fakeNextArbitrary } from '../check/arbitrary/generic/NextArbitraryHelpers';
+import {
+  assertGenerateProducesSameValueGivenSameSeed,
+  assertGenerateProducesCorrectValues,
+  assertGenerateProducesValuesFlaggedAsCanGenerate,
+  assertShrinkProducesSameValueWithoutInitialContext,
+  assertShrinkProducesCorrectValues,
+  assertShrinkProducesValuesFlaggedAsCanGenerate,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+
+import * as PartialRecordArbitraryBuilderMock from '../../../src/arbitrary/_internals/builders/PartialRecordArbitraryBuilder';
+
+function beforeEachHook() {
+  jest.resetModules();
+  jest.restoreAllMocks();
+  fc.configureGlobal({ beforeEach: beforeEachHook });
+}
+beforeEach(beforeEachHook);
+
+describe('record', () => {
+  const keyArb: fc.Arbitrary<any> = fc
+    .tuple(fc.string(), fc.boolean())
+    .map(([name, symbol]) => (symbol ? Symbol.for(name) : name));
+
+  it('should call buildPartialRecordArbitrary with keys=undefined when no constraints on keys', () =>
+    fc.assert(
+      fc.property(
+        fc.set(keyArb, { minLength: 1 }),
+        fc.constantFrom(...([undefined, {}] as const)),
+        (keys, constraints) => {
+          // Arrange
+          const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+          for (const k of keys) {
+            const { instance } = fakeNextArbitrary();
+            recordModel[k] = convertFromNext(instance);
+          }
+          const { instance } = fakeNextArbitrary<any>();
+          const buildPartialRecordArbitrary = jest.spyOn(
+            PartialRecordArbitraryBuilderMock,
+            'buildPartialRecordArbitrary'
+          );
+          buildPartialRecordArbitrary.mockReturnValue(convertFromNext(instance));
+
+          // Act
+          const arb = constraints !== undefined ? record(recordModel, constraints) : record(recordModel);
+
+          // Assert
+          expect(convertToNext(arb)).toBe(instance);
+          expect(buildPartialRecordArbitrary).toHaveBeenCalledTimes(1);
+          expect(buildPartialRecordArbitrary).toHaveBeenCalledWith(recordModel, undefined);
+        }
+      )
+    ));
+
+  it('should call buildPartialRecordArbitrary with keys=[] when constraints defines withDeletedKeys=true', () =>
+    fc.assert(
+      fc.property(fc.set(keyArb, { minLength: 1 }), (keys) => {
+        // Arrange
+        const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+        for (const k of keys) {
+          const { instance } = fakeNextArbitrary();
+          recordModel[k] = convertFromNext(instance);
+        }
+        const { instance } = fakeNextArbitrary<any>();
+        const buildPartialRecordArbitrary = jest.spyOn(
+          PartialRecordArbitraryBuilderMock,
+          'buildPartialRecordArbitrary'
+        );
+        buildPartialRecordArbitrary.mockReturnValue(convertFromNext(instance));
+
+        // Act
+        const arb = record(recordModel, { withDeletedKeys: true });
+
+        // Assert
+        expect(convertToNext(arb)).toBe(instance);
+        expect(buildPartialRecordArbitrary).toHaveBeenCalledTimes(1);
+        expect(buildPartialRecordArbitrary).toHaveBeenCalledWith(recordModel, []);
+      })
+    ));
+
+  it('should call buildPartialRecordArbitrary with keys=requiredKeys when constraints defines valid requiredKeys', () =>
+    fc.assert(
+      fc.property(fc.set(keyArb, { minLength: 1 }), fc.func(fc.boolean()), (keys, isRequired) => {
+        // Arrange
+        const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+        const requiredKeys: any[] = [];
+        for (const k of keys) {
+          const { instance } = fakeNextArbitrary();
+          recordModel[k] = convertFromNext(instance);
+          if (isRequired(k)) {
+            requiredKeys.push(k);
+          }
+        }
+        const { instance } = fakeNextArbitrary<any>();
+        const buildPartialRecordArbitrary = jest.spyOn(
+          PartialRecordArbitraryBuilderMock,
+          'buildPartialRecordArbitrary'
+        );
+        buildPartialRecordArbitrary.mockReturnValue(convertFromNext(instance));
+
+        // Act
+        const arb = record(recordModel, { requiredKeys });
+
+        // Assert
+        expect(convertToNext(arb)).toBe(instance);
+        expect(buildPartialRecordArbitrary).toHaveBeenCalledTimes(1);
+        expect(buildPartialRecordArbitrary).toHaveBeenCalledWith(recordModel, requiredKeys);
+      })
+    ));
+
+  it('should reject configurations specifying non existing keys as required', () =>
+    fc.assert(
+      fc.property(fc.set(keyArb, { minLength: 1 }), keyArb, (keys, requiredKey) => {
+        // Arrange
+        fc.pre(!keys.includes(requiredKey));
+        const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+        for (const k of keys) {
+          const { instance } = fakeNextArbitrary();
+          recordModel[k] = convertFromNext(instance);
+        }
+
+        // Act / Assert
+        expect(() =>
+          record(recordModel, {
+            requiredKeys: [requiredKey],
+          })
+        ).toThrowError();
+      })
+    ));
+
+  it('should reject configurations specifying both requiredKeys and withDeletedKeys (even undefined)', () =>
+    fc.assert(
+      fc.property(
+        fc.set(fc.record({ name: keyArb, required: fc.boolean() }), {
+          minLength: 1,
+          compare: (a, b) => a.name === b.name,
+        }),
+        fc.option(fc.constant(true), { nil: undefined }),
+        fc.option(fc.boolean(), { nil: undefined }),
+        (keys, withRequiredKeys, withDeletedKeys) => {
+          // Arrange
+          const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+          for (const k of keys) {
+            const { instance } = fakeNextArbitrary();
+            recordModel[k.name] = convertFromNext(instance);
+          }
+
+          // Act / Assert
+          expect(() =>
+            record(recordModel, {
+              requiredKeys: withRequiredKeys ? keys.filter((k) => k.required).map((k) => k.name) : undefined,
+              withDeletedKeys: withDeletedKeys,
+            })
+          ).toThrowError();
+        }
+      )
+    ));
+
+  it('should accept empty keys configurations with empty requiredKeys', () => {
+    // Arrange
+    const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+
+    // Act / Assert
+    expect(() => record(recordModel, { requiredKeys: [] })).not.toThrowError();
+  });
+
+  it.each`
+    keyName
+    ${'constructor'}
+    ${'toString'}
+    ${'__proto__'}
+  `('should reject configurations specifying non own properties ($keyName) as requiredKeys', ({ keyName }) => {
+    // Arrange
+    const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+
+    // Act / Assert
+    expect(() => record(recordModel, { requiredKeys: [keyName] })).toThrowError();
+  });
+
+  it('should reject configurations specifying non enumerable properties as requiredKeys', () => {
+    // Arrange
+    const keyName = 'k';
+    const recordModel: Record<string | symbol, Arbitrary<any>> = {};
+    Object.defineProperty(recordModel, keyName, { value: fc.boolean(), enumerable: false });
+
+    // Act / Assert
+    expect(() => record(recordModel, { requiredKeys: [keyName] })).toThrowError();
+  });
+});
+
+describe('record (integration)', () => {
+  type Meta = { key: any; valueStart: number; kept: boolean };
+  type Extra = [Meta[], RecordConstraints<any>];
+
+  const keyArb: fc.Arbitrary<any> = fc
+    .tuple(fc.string(), fc.boolean())
+    .map(([name, symbol]) => (symbol ? Symbol.for(name) : name));
+  const metaArbitrary = fc.set(
+    fc.record({
+      key: keyArb,
+      valueStart: fc.nat(1000),
+      kept: fc.boolean(),
+    }),
+    { compare: (v1, v2) => v1.key === v2.key }
+  );
+  const constraintsArbitrary = fc.oneof(
+    fc.record({ withDeletedKeys: fc.boolean() }, { requiredKeys: [] }),
+    fc.record({ withRequiredKeys: fc.constant<true>(true) }, { requiredKeys: [] })
+  );
+  const extraParameters: fc.Arbitrary<Extra> = fc
+    .tuple(metaArbitrary, constraintsArbitrary)
+    .map(([metas, constraintsMeta]: [Meta[], { withDeletedKeys?: boolean; withRequiredKeys?: true }]) => {
+      if ('withRequiredKeys' in constraintsMeta) {
+        return [metas, { requiredKeys: metas.filter((m) => m.kept).map((m) => m.key) }] as [Meta[], RecordConstraints];
+      }
+      return [metas, constraintsMeta] as [Meta[], RecordConstraints];
+    });
+
+  const isCorrect = (value: any, extra: Extra) => {
+    const [metas, constraints] = extra;
+    // Rq: getOwnPropertyNames will also get non enumerable properties, but there are none in our case
+    for (const k of [...Object.getOwnPropertyNames(value), ...Object.getOwnPropertySymbols(value)]) {
+      // generated object should not have more keys
+      if (metas.findIndex((m) => m.key === k) === -1) return false;
+    }
+    for (const m of metas) {
+      // optional keys can be missing in the generated instance
+      if (
+        'withDeletedKeys' in constraints &&
+        constraints.withDeletedKeys === true &&
+        !Object.prototype.hasOwnProperty.call(value, m.key)
+      ) {
+        continue;
+      }
+      if (
+        'requiredKeys' in constraints &&
+        constraints.requiredKeys !== undefined &&
+        !constraints.requiredKeys.includes(m.key) &&
+        !Object.prototype.hasOwnProperty.call(value, m.key)
+      ) {
+        continue;
+      }
+      // values are associated to the right key (if key required)
+      if (typeof value[m.key] !== 'number') return false;
+      if (value[m.key] < m.valueStart) return false;
+      if (value[m.key] > m.valueStart + 10) return false;
+    }
+    return true;
+  };
+
+  const recordBuilder = (extra: Extra) => {
+    const [metas, constraints] = extra;
+    const recordModel: Record<string | symbol, Arbitrary<number>> = {};
+    for (const m of metas) {
+      const instance = new FakeIntegerArbitrary(m.valueStart, 10);
+      recordModel[m.key] = convertFromNext(instance);
+    }
+    return convertToNext(record(recordModel, constraints));
+  };
+
+  it('should generate the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(recordBuilder, { extraParameters });
+  });
+
+  it('should only generate correct values', () => {
+    assertGenerateProducesCorrectValues(recordBuilder, isCorrect, { extraParameters });
+  });
+
+  it('should recognize values that would have been generated using it during generate', () => {
+    assertGenerateProducesValuesFlaggedAsCanGenerate(recordBuilder, { extraParameters });
+  });
+
+  it('should shrink towards the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(recordBuilder, { extraParameters });
+  });
+
+  it('should be able to shrink without any context', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(recordBuilder, { extraParameters });
+  });
+
+  it('should only shrink towards correct values', () => {
+    assertShrinkProducesCorrectValues(recordBuilder, isCorrect, { extraParameters });
+  });
+
+  it('should recognize values that would have been generated using it during shrink', () => {
+    assertShrinkProducesValuesFlaggedAsCanGenerate(recordBuilder, { extraParameters });
+  });
+});
diff --git a/test/unit/arbitrary/string.spec.ts b/test/unit/arbitrary/string.spec.ts
index a4899895..5e624002 100644
--- a/test/unit/arbitrary/string.spec.ts
+++ b/test/unit/arbitrary/string.spec.ts
@@ -74,7 +74,7 @@ describe('string (integration)', () => {
   `('should be able to generate $source with fc.string()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(string());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(true);
@@ -87,7 +87,7 @@ describe('string (integration)', () => {
   `('should not be able to generate $source with fc.string()', ({ source }) => {
     // Arrange / Act
     const arb = convertToNext(string());
-    const out = arb.canGenerate(source);
+    const out = arb.canShrinkWithoutContext(source);
 
     // Assert
     expect(out).toBe(false);
diff --git a/test/unit/arbitrary/stringOf.spec.ts b/test/unit/arbitrary/stringOf.spec.ts
index 2a589067..1c4e44c5 100644
--- a/test/unit/arbitrary/stringOf.spec.ts
+++ b/test/unit/arbitrary/stringOf.spec.ts
@@ -79,7 +79,7 @@ class PatternsArbitrary extends NextArbitrary<string> {
   generate(mrng: Random): NextValue<string> {
     return new NextValue(this.patterns[mrng.nextInt(0, this.patterns.length - 1)]);
   }
-  canGenerate(value: unknown): value is string {
+  canShrinkWithoutContext(value: unknown): value is string {
     if (typeof value !== 'string') return false;
     return this.patterns.includes(value);
   }
diff --git a/test/unit/check/arbitrary/FloatingPointArbitrary.spec.ts b/test/unit/check/arbitrary/FloatingPointArbitrary.spec.ts
index 75237908..e06700ff 100644
--- a/test/unit/check/arbitrary/FloatingPointArbitrary.spec.ts
+++ b/test/unit/check/arbitrary/FloatingPointArbitrary.spec.ts
@@ -11,7 +11,7 @@ import { generateOneValue } from './generic/GenerateOneValue';
 
 const MAX_TRIES = 100;
 describe('FloatingPointArbitrary', () => {
-  const canGenerateFloatingPoint = (mrng: Random, arb: Arbitrary<number>) => {
+  const canShrinkWithoutContextFloatingPoint = (mrng: Random, arb: Arbitrary<number>) => {
     for (let idx = 0; idx != MAX_TRIES; ++idx) {
       const g = arb.generate(mrng).value;
       if (g != Math.round(g)) {
@@ -26,7 +26,7 @@ describe('FloatingPointArbitrary', () => {
       fc.assert(
         fc.property(fc.integer(), (seed) => {
           const mrng = stubRng.mutable.fastincrease(seed);
-          return canGenerateFloatingPoint(mrng, float());
+          return canShrinkWithoutContextFloatingPoint(mrng, float());
         })
       ));
     describe('Given no constraints [between 0 (included) and 1 (excluded)]', () => {
@@ -95,7 +95,7 @@ describe('FloatingPointArbitrary', () => {
       fc.assert(
         fc.property(fc.integer(), (seed) => {
           const mrng = stubRng.mutable.fastincrease(seed);
-          return canGenerateFloatingPoint(mrng, double());
+          return canShrinkWithoutContextFloatingPoint(mrng, double());
         })
       ));
     describe('Given no constraints [between 0 (included) and 1 (excluded)]', () => {
diff --git a/test/unit/check/arbitrary/definition/ConverterFromNext.itest.spec.ts b/test/unit/check/arbitrary/definition/ConverterFromNext.itest.spec.ts
index 51ad02ea..e9a74b0c 100644
--- a/test/unit/check/arbitrary/definition/ConverterFromNext.itest.spec.ts
+++ b/test/unit/check/arbitrary/definition/ConverterFromNext.itest.spec.ts
@@ -22,7 +22,7 @@ describe('ConverterFromNext', () => {
             const context = mrng.nextInt(0, 1);
             return new NextValue([v1, v2], context);
           }
-          canGenerate(_value: unknown): _value is [number, number] {
+          canShrinkWithoutContext(_value: unknown): _value is [number, number] {
             throw new Error('Unused in this context');
           }
           shrink(value: [number, number], context?: unknown): Stream<NextValue<[number, number]>> {
diff --git a/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts b/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
index 844ef4a1..f56ca5cf 100644
--- a/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
@@ -19,7 +19,7 @@ describe('ConverterFromNext', () => {
         generate(_mrng: Random): NextValue<number> {
           throw new Error('Method not implemented.');
         }
-        canGenerate(_value: unknown): _value is number {
+        canShrinkWithoutContext(_value: unknown): _value is number {
           throw new Error('Method not implemented');
         }
         shrink(_value: number, _context?: unknown): Stream<NextValue<number>> {
@@ -57,7 +57,7 @@ describe('ConverterFromNext', () => {
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = (_v: unknown): _v is number => {
+        canShrinkWithoutContext = (_v: unknown): _v is number => {
           throw new Error('Unexpected call');
         };
         shrink = shrink;
@@ -82,7 +82,7 @@ describe('ConverterFromNext', () => {
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = (_v: unknown): _v is number => {
+        canShrinkWithoutContext = (_v: unknown): _v is number => {
           throw new Error('Unexpected call');
         };
         shrink = shrink;
@@ -112,7 +112,7 @@ describe('ConverterFromNext', () => {
       const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedFirstValue, expectedFirstContext));
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = (_v: unknown): _v is number => {
+        canShrinkWithoutContext = (_v: unknown): _v is number => {
           throw new Error('Unexpected call');
         };
         shrink = shrink;
@@ -149,7 +149,7 @@ describe('ConverterFromNext', () => {
       const generate = jest.fn().mockReturnValueOnce(new NextValue(1, Symbol()));
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = (_v: unknown): _v is number => {
+        canShrinkWithoutContext = (_v: unknown): _v is number => {
           throw new Error('Unexpected call');
         };
         shrink = shrink;
@@ -174,12 +174,12 @@ describe('ConverterFromNext', () => {
     it('should call filter directly on the passed instance and convert its result', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const filter = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         filter = filter;
       }
@@ -202,12 +202,12 @@ describe('ConverterFromNext', () => {
     it('should unwrap instances of ConverterFromNext returned by filter', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const filter = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         filter = filter;
       }
@@ -233,12 +233,12 @@ describe('ConverterFromNext', () => {
     it('should call map directly on the passed instance and convert its result', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const map = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         map = map;
       }
@@ -261,12 +261,12 @@ describe('ConverterFromNext', () => {
     it('should unwrap instances of ConverterFromNext returned by map', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const map = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         map = map;
       }
@@ -292,12 +292,12 @@ describe('ConverterFromNext', () => {
     it('should call chain directly on the passed instance and convert its result', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const chain = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         chain = chain;
       }
@@ -323,12 +323,12 @@ describe('ConverterFromNext', () => {
     it('should call noShrink directly on the passed instance and convert its result', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const noShrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         noShrink = noShrink;
       }
@@ -351,12 +351,12 @@ describe('ConverterFromNext', () => {
     it('should call noBias directly on the passed instance and convert its result', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const noBias = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         noBias = noBias;
       }
diff --git a/test/unit/check/arbitrary/definition/ConverterToNext.utest.spec.ts b/test/unit/check/arbitrary/definition/ConverterToNext.utest.spec.ts
index 725b2690..12715925 100644
--- a/test/unit/check/arbitrary/definition/ConverterToNext.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/ConverterToNext.utest.spec.ts
@@ -35,7 +35,7 @@ describe('ConverterToNext', () => {
         generate(_mrng: Random): NextValue<number> {
           throw new Error('Method not implemented.');
         }
-        canGenerate(_value: unknown): _value is number {
+        canShrinkWithoutContext(_value: unknown): _value is number {
           throw new Error('Method not implemented.');
         }
         shrink(_value: number, _context?: unknown): Stream<NextValue<number>> {
@@ -180,7 +180,7 @@ describe('ConverterToNext', () => {
     it('should unwrap instances of ConverterFromNext returned by filter', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const filter = jest.fn();
       class MyArbitrary extends Arbitrary<number> {
@@ -189,7 +189,7 @@ describe('ConverterToNext', () => {
       }
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
       }
       const originalInstance = new MyArbitrary();
@@ -235,7 +235,7 @@ describe('ConverterToNext', () => {
     it('should unwrap instances of ConverterFromNext returned by map', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const map = jest.fn();
       class MyArbitrary extends Arbitrary<number> {
@@ -244,7 +244,7 @@ describe('ConverterToNext', () => {
       }
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
-        canGenerate = canGenerate as any as (v: unknown) => v is number;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (v: unknown) => v is number;
         shrink = shrink;
         map = map;
       }
diff --git a/test/unit/check/arbitrary/definition/Converters.itest.spec.ts b/test/unit/check/arbitrary/definition/Converters.itest.spec.ts
index 9698555b..9d52ca43 100644
--- a/test/unit/check/arbitrary/definition/Converters.itest.spec.ts
+++ b/test/unit/check/arbitrary/definition/Converters.itest.spec.ts
@@ -34,7 +34,7 @@ describe('Converters', () => {
       generate(_mrng: Random): NextValue<number> {
         throw new Error('Method not implemented.');
       }
-      canGenerate(_value: unknown): _value is number {
+      canShrinkWithoutContext(_value: unknown): _value is number {
         throw new Error('Method not implemented.');
       }
       shrink(_value: number, _context?: unknown): Stream<NextValue<number>> {
diff --git a/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts b/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
index db403845..99d351c1 100644
--- a/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
+++ b/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
@@ -18,7 +18,7 @@ describe('NextArbitrary', () => {
         generate(_mrng: Random): NextValue<number> {
           return new NextValue(10, { step: 2 });
         }
-        canGenerate(value: unknown): value is number {
+        canShrinkWithoutContext(value: unknown): value is number {
           throw new Error('No call expected in current scenario');
         }
         shrink(value: number, context?: unknown): Stream<NextValue<number>> {
@@ -109,7 +109,7 @@ describe('NextArbitrary', () => {
         generate(_mrng: Random): NextValue<MyArbitraryOutput> {
           return new NextValue(this.create(10));
         }
-        canGenerate(value: unknown): value is MyArbitraryOutput {
+        canShrinkWithoutContext(value: unknown): value is MyArbitraryOutput {
           throw new Error('No call expected in current scenario');
         }
         shrink(v: MyArbitraryOutput, _context?: unknown): Stream<NextValue<MyArbitraryOutput>> {
@@ -148,7 +148,7 @@ describe('NextArbitrary', () => {
         generate(_mrng: Random): NextValue<number> {
           return new NextValue(5, { step: 2 });
         }
-        canGenerate(value: unknown): value is number {
+        canShrinkWithoutContext(value: unknown): value is number {
           throw new Error('No call expected in current scenario');
         }
         shrink(value: number, context?: unknown): Stream<NextValue<number>> {
@@ -173,7 +173,7 @@ describe('NextArbitrary', () => {
             value: this.value,
           });
         }
-        canGenerate(value: unknown): value is number[] {
+        canShrinkWithoutContext(value: unknown): value is number[] {
           throw new Error('No call expected in current scenario');
         }
         shrink(value: number[], context?: unknown): Stream<NextValue<number[]>> {
@@ -224,7 +224,7 @@ describe('NextArbitrary', () => {
         generate(_mrng: Random): NextValue<number> {
           return new NextValue(10, { step: 3 });
         }
-        canGenerate(value: unknown): value is number {
+        canShrinkWithoutContext(value: unknown): value is number {
           throw new Error('No call expected in current scenario');
         }
         shrink(value: number, context?: unknown): Stream<NextValue<number>> {
diff --git a/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts b/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
index c30a42af..e173d875 100644
--- a/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
@@ -62,7 +62,7 @@ describe('NextArbitrary', () => {
         generate(): NextValue<any> {
           throw new Error('Not implemented.');
         }
-        canGenerate(value: unknown): value is any {
+        canShrinkWithoutContext(value: unknown): value is any {
           throw new Error('Not implemented.');
         }
         shrink(): Stream<NextValue<any>> {
@@ -81,7 +81,7 @@ describe('NextArbitrary', () => {
         generate() {
           throw new Error('Not implemented');
         },
-        canGenerate(v): v is any {
+        canShrinkWithoutContext(v): v is any {
           throw new Error('Not implemented');
         },
         shrink() {
@@ -114,7 +114,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choice1 = new NextValue(1, Symbol());
       const choice2 = new NextValue(2, Symbol());
@@ -127,7 +127,7 @@ describe('NextArbitrary', () => {
         .mockReturnValueOnce(choice4);
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -143,7 +143,7 @@ describe('NextArbitrary', () => {
     it('should filter the values produced by the original arbitrary on shrink', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const valueToShrink = 5;
       const contextToShrink = Symbol();
@@ -155,7 +155,7 @@ describe('NextArbitrary', () => {
       shrink.mockReturnValueOnce(Stream.of(choice1, choice2, choice3, choice4, choice5));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -169,36 +169,36 @@ describe('NextArbitrary', () => {
     });
 
     it.each`
-      canGenerateOutput | predicateOutput | expected
-      ${false}          | ${false}        | ${false}
-      ${false}          | ${true}         | ${false}
-      ${false}          | ${false}        | ${false}
-      ${true}           | ${true}         | ${true}
+      canShrinkWithoutContextOutput | predicateOutput | expected
+      ${false}                      | ${false}        | ${false}
+      ${false}                      | ${true}         | ${false}
+      ${false}                      | ${false}        | ${false}
+      ${true}                       | ${true}         | ${true}
     `(
       'should check underlying arbitrary then predicate to know if the value could have been generated',
-      ({ canGenerateOutput, predicateOutput, expected }) => {
+      ({ canShrinkWithoutContextOutput, predicateOutput, expected }) => {
         // Arrange
         const requestedValue = Symbol();
         const generate = jest.fn();
-        const canGenerate = jest.fn();
+        const canShrinkWithoutContext = jest.fn();
         const shrink = jest.fn();
         const predicate = jest.fn();
         class MyNextArbitrary extends NextArbitrary<any> {
           generate = generate;
-          canGenerate = canGenerate as any as (value: unknown) => value is any;
+          canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
           shrink = shrink;
         }
-        canGenerate.mockReturnValueOnce(canGenerateOutput);
+        canShrinkWithoutContext.mockReturnValueOnce(canShrinkWithoutContextOutput);
         predicate.mockReturnValueOnce(predicateOutput);
 
         // Act
         const arb = new MyNextArbitrary().filter(predicate);
-        const out = arb.canGenerate(requestedValue);
+        const out = arb.canShrinkWithoutContext(requestedValue);
 
         // Assert
         expect(out).toBe(expected);
-        expect(canGenerate).toHaveBeenCalledWith(requestedValue);
-        if (canGenerateOutput) {
+        expect(canShrinkWithoutContext).toHaveBeenCalledWith(requestedValue);
+        if (canShrinkWithoutContextOutput) {
           expect(predicate).toHaveBeenCalledWith(requestedValue);
         } else {
           expect(predicate).not.toHaveBeenCalledWith(requestedValue);
@@ -213,13 +213,13 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choice = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choice);
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -236,13 +236,13 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choice = new NextValue({ source: 1, [cloneMethod]: () => choice.value_ }, Symbol());
       generate.mockReturnValueOnce(choice);
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -261,7 +261,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 42;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const source = new NextValue(69, Symbol());
       generate.mockReturnValueOnce(source);
@@ -271,7 +271,7 @@ describe('NextArbitrary', () => {
       shrink.mockReturnValueOnce(Stream.of(choice1, choice2, choice3));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -289,7 +289,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 42;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const source = new NextValue(69, Symbol());
       generate.mockReturnValueOnce(source);
@@ -302,7 +302,7 @@ describe('NextArbitrary', () => {
       shrink.mockReturnValueOnce(Stream.of(choice21, choice22));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -323,7 +323,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const source = new NextValue({ source: 1, [cloneMethod]: () => source.value_ }, Symbol());
       generate.mockReturnValueOnce(source);
@@ -332,7 +332,7 @@ describe('NextArbitrary', () => {
       shrink.mockReturnValueOnce(Stream.of(choice1, choice2));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -350,34 +350,34 @@ describe('NextArbitrary', () => {
       expect(hasCloneMethod(shrinksValues[1].value)).toBe(true);
     });
 
-    it('should always return false for canGenerate when not provided any unmapper function', () => {
+    it('should always return false for canShrinkWithoutContext when not provided any unmapper function', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
       // Act
       const arb = new MyNextArbitrary().map(() => '');
-      const out = arb.canGenerate('');
+      const out = arb.canShrinkWithoutContext('');
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it('should return empty stream when shrinking without any context and not provided any unmapper function', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
@@ -388,7 +388,7 @@ describe('NextArbitrary', () => {
       // Assert
       expect([...shrinks]).toHaveLength(0);
       expect(shrink).not.toHaveBeenCalled();
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it.each`
@@ -396,38 +396,38 @@ describe('NextArbitrary', () => {
       ${false}
       ${true}
     `(
-      'should try to unmap the value then call source arbitrary on canGenerate when provided a successful unmapper function',
+      'should try to unmap the value then call source arbitrary on canShrinkWithoutContext when provided a successful unmapper function',
       ({ outputCanGenerate }) => {
         // Arrange
         const generate = jest.fn();
-        const canGenerate = jest.fn().mockReturnValue(outputCanGenerate);
+        const canShrinkWithoutContext = jest.fn().mockReturnValue(outputCanGenerate);
         const shrink = jest.fn();
         const originalValue = Symbol();
         const unmapperOutput = Symbol();
         const unmapper = jest.fn().mockReturnValue(unmapperOutput);
         class MyNextArbitrary extends NextArbitrary<any> {
           generate = generate;
-          canGenerate = canGenerate as any as (value: unknown) => value is any;
+          canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
           shrink = shrink;
         }
 
         // Act
         const arb = new MyNextArbitrary().map(() => Symbol(), unmapper);
-        const out = arb.canGenerate(originalValue);
+        const out = arb.canShrinkWithoutContext(originalValue);
 
         // Assert
         expect(out).toBe(outputCanGenerate);
         expect(unmapper).toHaveBeenCalledTimes(1);
         expect(unmapper).toHaveBeenCalledWith(originalValue);
-        expect(canGenerate).toHaveBeenCalledTimes(1);
-        expect(canGenerate).toHaveBeenCalledWith(unmapperOutput);
+        expect(canShrinkWithoutContext).toHaveBeenCalledTimes(1);
+        expect(canShrinkWithoutContext).toHaveBeenCalledWith(unmapperOutput);
       }
     );
 
     it('should try to unmap the value and stop on error in case of failing unmapper function', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       const originalValue = Symbol();
       const unmapper = jest.fn().mockImplementation(() => {
@@ -435,19 +435,19 @@ describe('NextArbitrary', () => {
       });
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
       // Act
       const arb = new MyNextArbitrary().map(() => Symbol(), unmapper);
-      const out = arb.canGenerate(originalValue);
+      const out = arb.canShrinkWithoutContext(originalValue);
 
       // Assert
       expect(out).toBe(false);
       expect(unmapper).toHaveBeenCalledTimes(1);
       expect(unmapper).toHaveBeenCalledWith(originalValue);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it('should return a mapped version of the stream produced by the source arbitrary for the unmapped value when provided an unmapper function', () => {
@@ -458,14 +458,14 @@ describe('NextArbitrary', () => {
         new NextValue('tutu')
       );
       const generate = jest.fn();
-      const canGenerate = jest.fn().mockReturnValue(true);
+      const canShrinkWithoutContext = jest.fn().mockReturnValue(true);
       const shrink = jest.fn().mockReturnValueOnce(expectedStreamValuesFromSource);
       const originalValue = Symbol();
       const unmapperOutput = 'tata';
       const unmapper = jest.fn().mockReturnValue('tata');
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
@@ -477,8 +477,8 @@ describe('NextArbitrary', () => {
       expect(shrinks.map((s) => s.value)).toEqual([Symbol.for('titi'), Symbol.for('toto'), Symbol.for('tutu')]);
       expect(unmapper).toHaveBeenCalledTimes(1);
       expect(unmapper).toHaveBeenCalledWith(originalValue);
-      expect(canGenerate).toHaveBeenCalledTimes(1);
-      expect(canGenerate).toHaveBeenCalledWith(unmapperOutput);
+      expect(canShrinkWithoutContext).toHaveBeenCalledTimes(1);
+      expect(canShrinkWithoutContext).toHaveBeenCalledWith(unmapperOutput);
       expect(shrink).toHaveBeenCalledTimes(1);
       expect(shrink).toHaveBeenCalledWith(unmapperOutput);
     });
@@ -491,14 +491,14 @@ describe('NextArbitrary', () => {
         new NextValue('tutu')
       );
       const generate = jest.fn();
-      const canGenerate = jest.fn().mockReturnValue(false);
+      const canShrinkWithoutContext = jest.fn().mockReturnValue(false);
       const shrink = jest.fn().mockReturnValueOnce(expectedStreamValuesFromSource);
       const originalValue = Symbol();
       const unmapperOutput = 'tata';
       const unmapper = jest.fn().mockReturnValue('tata');
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
@@ -510,8 +510,8 @@ describe('NextArbitrary', () => {
       expect(shrinks.map((s) => s.value)).toEqual([]);
       expect(unmapper).toHaveBeenCalledTimes(1);
       expect(unmapper).toHaveBeenCalledWith(originalValue);
-      expect(canGenerate).toHaveBeenCalledTimes(1);
-      expect(canGenerate).toHaveBeenCalledWith(unmapperOutput);
+      expect(canShrinkWithoutContext).toHaveBeenCalledTimes(1);
+      expect(canShrinkWithoutContext).toHaveBeenCalledWith(unmapperOutput);
       expect(shrink).not.toHaveBeenCalled();
     });
 
@@ -523,7 +523,7 @@ describe('NextArbitrary', () => {
         new NextValue('tutu')
       );
       const generate = jest.fn();
-      const canGenerate = jest.fn().mockReturnValue(false);
+      const canShrinkWithoutContext = jest.fn().mockReturnValue(false);
       const shrink = jest.fn().mockReturnValueOnce(expectedStreamValuesFromSource);
       const originalValue = Symbol();
       const unmapper = jest.fn().mockImplementation(() => {
@@ -531,7 +531,7 @@ describe('NextArbitrary', () => {
       });
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
@@ -546,23 +546,23 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choiceRoot = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choiceRoot);
       const generateChained = jest.fn();
-      const canGenerateChained = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContextChained = jest.fn() as any as (value: unknown) => value is any;
       const shrinkChained = jest.fn();
       const choiceChained = new NextValue(50, Symbol());
       generateChained.mockReturnValueOnce(choiceChained);
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
       class MyNextChainedArbitrary extends NextArbitrary<any> {
         generate = generateChained;
-        canGenerate = canGenerateChained;
+        canShrinkWithoutContext = canShrinkWithoutContextChained;
         shrink = shrinkChained;
       }
       const chainer = jest.fn();
@@ -583,7 +583,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choiceRoot = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choiceRoot);
@@ -592,7 +592,7 @@ describe('NextArbitrary', () => {
       const shrinkRoot3 = new NextValue(15, Symbol());
       shrink.mockReturnValueOnce(Stream.of(shrinkRoot1, shrinkRoot2, shrinkRoot3));
       const generateChained = jest.fn();
-      const canGenerateChained = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContextChained = jest.fn() as any as (value: unknown) => value is any;
       const shrinkChained = jest.fn();
       const choiceChained = new NextValue(50, Symbol());
       const choiceShrink1Chained = new NextValue(58, Symbol()); // chain will be called for each sub-shrink of root
@@ -608,12 +608,12 @@ describe('NextArbitrary', () => {
       shrinkChained.mockReturnValueOnce(Stream.of(shrinkChained1, shrinkChained2));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
       class MyNextChainedArbitrary extends NextArbitrary<any> {
         generate = generateChained;
-        canGenerate = canGenerateChained;
+        canShrinkWithoutContext = canShrinkWithoutContextChained;
         shrink = shrinkChained;
       }
       const chainer = jest.fn();
@@ -643,7 +643,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choiceRoot = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choiceRoot);
@@ -654,7 +654,7 @@ describe('NextArbitrary', () => {
       const shrinkRoot11 = new NextValue(310, Symbol());
       shrink.mockReturnValueOnce(Stream.of(shrinkRoot11));
       const generateChained = jest.fn();
-      const canGenerateChained = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContextChained = jest.fn() as any as (value: unknown) => value is any;
       const shrinkChained = jest.fn();
       const choiceChained = new NextValue(50, Symbol());
       const choiceShrink1Chained = new NextValue(58, Symbol()); // chain will be called for each iterated sub-shrink of root (->10)
@@ -671,12 +671,12 @@ describe('NextArbitrary', () => {
       shrinkChained.mockReturnValueOnce(Stream.of(shrinkChained11, shrinkChained12));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
       class MyNextChainedArbitrary extends NextArbitrary<any> {
         generate = generateChained;
-        canGenerate = canGenerateChained;
+        canShrinkWithoutContext = canShrinkWithoutContextChained;
         shrink = shrinkChained;
       }
       const chainer = jest.fn();
@@ -706,7 +706,7 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choiceRoot = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choiceRoot);
@@ -714,7 +714,7 @@ describe('NextArbitrary', () => {
       const shrinkRoot2 = new NextValue(11, Symbol());
       shrink.mockReturnValueOnce(Stream.of(shrinkRoot1, shrinkRoot2));
       const generateChained = jest.fn();
-      const canGenerateChained = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContextChained = jest.fn() as any as (value: unknown) => value is any;
       const shrinkChained = jest.fn();
       const choiceChained = new NextValue(50, Symbol());
       const choiceShrink1Chained = new NextValue(58, Symbol());
@@ -731,12 +731,12 @@ describe('NextArbitrary', () => {
       shrinkChained.mockReturnValueOnce(Stream.of(shrinkChained11, shrinkChained12));
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
       class MyNextChainedArbitrary extends NextArbitrary<any> {
         generate = generateChained;
-        canGenerate = canGenerateChained;
+        canShrinkWithoutContext = canShrinkWithoutContextChained;
         shrink = shrinkChained;
       }
       const chainer = jest.fn();
@@ -753,34 +753,34 @@ describe('NextArbitrary', () => {
       expect(shrink).toHaveBeenCalledTimes(1); // not called back on second call to shrink
     });
 
-    it('should always return false for canGenerate when not provided any unchain function', () => {
+    it('should always return false for canShrinkWithoutContext when not provided any unchain function', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
       // Act
       const arb = new MyNextArbitrary().chain(() => new MyNextArbitrary());
-      const out = arb.canGenerate('');
+      const out = arb.canShrinkWithoutContext('');
 
       // Assert
       expect(out).toBe(false);
-      expect(canGenerate).not.toHaveBeenCalled();
+      expect(canShrinkWithoutContext).not.toHaveBeenCalled();
     });
 
     it('should return empty stream when shrinking without any context and not provided any unchainer function', () => {
       // Arrange
       const generate = jest.fn();
-      const canGenerate = jest.fn();
+      const canShrinkWithoutContext = jest.fn();
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate as any as (value: unknown) => value is any;
+        canShrinkWithoutContext = canShrinkWithoutContext as any as (value: unknown) => value is any;
         shrink = shrink;
       }
 
@@ -799,13 +799,13 @@ describe('NextArbitrary', () => {
       // Arrange
       const expectedBiasFactor = 48;
       const generate = jest.fn();
-      const canGenerate = jest.fn() as any as (value: unknown) => value is any;
+      const canShrinkWithoutContext = jest.fn() as any as (value: unknown) => value is any;
       const shrink = jest.fn();
       const choice = new NextValue(1, Symbol());
       generate.mockReturnValueOnce(choice);
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate = canGenerate;
+        canShrinkWithoutContext = canShrinkWithoutContext;
         shrink = shrink;
       }
 
@@ -825,7 +825,7 @@ describe('NextArbitrary', () => {
         generate(): NextValue<any> {
           throw new Error('Not implemented.');
         }
-        canGenerate(value: unknown): value is any {
+        canShrinkWithoutContext(value: unknown): value is any {
           throw new Error('Not implemented.');
         }
         shrink = shrink;
@@ -847,7 +847,7 @@ describe('NextArbitrary', () => {
         generate(): NextValue<any> {
           throw new Error('Not implemented.');
         }
-        canGenerate(value: unknown): value is any {
+        canShrinkWithoutContext(value: unknown): value is any {
           throw new Error('Not implemented.');
         }
         shrink(): Stream<NextValue<any>> {
@@ -871,7 +871,7 @@ describe('NextArbitrary', () => {
       const generate = jest.fn();
       class MyNextArbitrary extends NextArbitrary<any> {
         generate = generate;
-        canGenerate(value: unknown): value is any {
+        canShrinkWithoutContext(value: unknown): value is any {
           throw new Error('Not implemented.');
         }
         shrink(): Stream<NextValue<any>> {
@@ -895,7 +895,7 @@ describe('NextArbitrary', () => {
         generate(): NextValue<any> {
           throw new Error('Not implemented.');
         }
-        canGenerate(value: unknown): value is any {
+        canShrinkWithoutContext(value: unknown): value is any {
           throw new Error('Not implemented.');
         }
         shrink(): Stream<NextValue<any>> {
diff --git a/test/unit/check/arbitrary/generic/NextArbitraryAssertions.ts b/test/unit/check/arbitrary/generic/NextArbitraryAssertions.ts
index 48039e43..17d771f8 100644
--- a/test/unit/check/arbitrary/generic/NextArbitraryAssertions.ts
+++ b/test/unit/check/arbitrary/generic/NextArbitraryAssertions.ts
@@ -189,8 +189,8 @@ export function assertGenerateEquivalentTo<T, U = never>(
 // The assertions above can be configured to push generators even further. They ensure more complex invariants.
 // Following assertions are mostly derived from the one above.
 // > assertShrinkProducesSameValueGivenSameSeed with option {noInitialContext:true}
-// > assertGenerateProducesCorrectValues with option isCorrect: (v, _, arb) => arb.canGenerate(v)
-// > assertShrinkProducesCorrectValues with option (v, _, arb) => arb.canGenerate(v)
+// > assertGenerateProducesCorrectValues with option isCorrect: (v, _, arb) => arb.canShrinkWithoutContext(v)
+// > assertShrinkProducesCorrectValues with option (v, _, arb) => arb.canShrinkWithoutContext(v)
 
 export function assertShrinkProducesSameValueWithoutInitialContext<T, U = never>(
   arbitraryBuilder: (extraParameters: U) => NextArbitrary<T>,
@@ -210,7 +210,7 @@ export function assertGenerateProducesValuesFlaggedAsCanGenerate<T, U = never>(
     assertParameters?: fc.Parameters<unknown>;
   } = {}
 ): void {
-  return assertGenerateProducesCorrectValues(arbitraryBuilder, (v, _, arb) => arb.canGenerate(v), options);
+  return assertGenerateProducesCorrectValues(arbitraryBuilder, (v, _, arb) => arb.canShrinkWithoutContext(v), options);
 }
 
 export function assertShrinkProducesValuesFlaggedAsCanGenerate<T, U = never>(
@@ -220,7 +220,7 @@ export function assertShrinkProducesValuesFlaggedAsCanGenerate<T, U = never>(
     assertParameters?: fc.Parameters<unknown>;
   } = {}
 ): void {
-  return assertShrinkProducesCorrectValues(arbitraryBuilder, (v, _, arb) => arb.canGenerate(v), options);
+  return assertShrinkProducesCorrectValues(arbitraryBuilder, (v, _, arb) => arb.canShrinkWithoutContext(v), options);
 }
 
 export function assertShrinkProducesStrictlySmallerValue<T, U = never>(
diff --git a/test/unit/check/arbitrary/generic/NextArbitraryHelpers.ts b/test/unit/check/arbitrary/generic/NextArbitraryHelpers.ts
index a2ce4890..5f6e4262 100644
--- a/test/unit/check/arbitrary/generic/NextArbitraryHelpers.ts
+++ b/test/unit/check/arbitrary/generic/NextArbitraryHelpers.ts
@@ -12,7 +12,7 @@ export function fakeNextArbitraryClass<T = any>(): { Class: new () => NextArbitr
   NextArbitrary<T>
 > {
   const generate = jest.fn();
-  const canGenerate = jest.fn() as any as ((value: unknown) => value is T) &
+  const canShrinkWithoutContext = jest.fn() as any as ((value: unknown) => value is T) &
     MockWithArgs<(value: unknown) => value is T>;
   const shrink = jest.fn();
   const filter = jest.fn();
@@ -23,7 +23,7 @@ export function fakeNextArbitraryClass<T = any>(): { Class: new () => NextArbitr
   mocked;
   class FakeNextArbitrary extends NextArbitrary<T> {
     generate = generate;
-    canGenerate = canGenerate;
+    canShrinkWithoutContext = canShrinkWithoutContext;
     shrink = shrink;
     filter = filter;
     map = map;
@@ -31,7 +31,7 @@ export function fakeNextArbitraryClass<T = any>(): { Class: new () => NextArbitr
     noShrink = noShrink;
     noBias = noBias;
   }
-  return { Class: FakeNextArbitrary, generate, canGenerate, shrink, filter, map, chain, noShrink, noBias };
+  return { Class: FakeNextArbitrary, generate, canShrinkWithoutContext, shrink, filter, map, chain, noShrink, noBias };
 }
 
 /**
@@ -45,7 +45,7 @@ export function fakeNextArbitrary<T = any>(): { instance: NextArbitrary<T> } & M
 /**
  * Fake instance with the following capabilities:
  * - shrink to strictly smaller values
- * - fully implemented canGenerate
+ * - fully implemented canShrinkWithoutContext
  * - take bias into account on generate
  * - context less shrink like context full (for first iteration only)
  */
@@ -61,7 +61,7 @@ export class FakeIntegerArbitrary extends NextArbitrary<number> {
     const maxLimit = biasFactor !== undefined ? Math.max(minRange, maxRange - biasFactor) : maxRange;
     return new NextValue(mrng.nextInt(0, maxLimit) + this.offset, { step: 2 });
   }
-  canGenerate(value: unknown): value is number {
+  canShrinkWithoutContext(value: unknown): value is number {
     return (
       typeof value === 'number' &&
       value >= this.offset &&
diff --git a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
index 41779b22..e1461284 100644
--- a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
+++ b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
@@ -140,7 +140,7 @@ describe('arrayInt64', () => {
       ));
   });
 
-  describe('canGenerate', () => {
+  describe('canShrinkWithoutContext', () => {
     it('should recognize any value it could have generated', () =>
       fc.assert(
         fc.property(
@@ -156,7 +156,7 @@ describe('arrayInt64', () => {
 
             // Act
             const arb = arrayInt64(toArrayInt64(min, negMin), toArrayInt64(max, negMax));
-            const out = arb.canGenerate(toArrayInt64(mid, negMid));
+            const out = arb.canShrinkWithoutContext(toArrayInt64(mid, negMid));
 
             // Assert
             expect(out).toBe(true);
@@ -183,7 +183,7 @@ describe('arrayInt64', () => {
 
             // Act
             const arb = arrayInt64(toArrayInt64(min, negMin), toArrayInt64(max, negMax));
-            const out = arb.canGenerate(toArrayInt64(selected, negSelected));
+            const out = arb.canShrinkWithoutContext(toArrayInt64(selected, negSelected));
 
             // Assert
             expect(out).toBe(false);