diff --git a/src/faker.ts b/src/faker.ts index 77630d29ee7..0b03788b3be 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -125,6 +125,10 @@ export class Faker extends SimpleFaker { * Specify this only if you want to use it to achieve a specific goal, * such as sharing the same random generator with other instances/tools. * Defaults to faker's Mersenne Twister based pseudo random number generator. + * @param options.seed The initial seed to use. + * The seed can be used to generate reproducible values. + * Refer to the `seed()` method for more information. + * Defaults to a random seed. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -157,8 +161,18 @@ export class Faker extends SimpleFaker { * @default generateMersenne53Randomizer() */ randomizer?: Randomizer; + + /** + * The initial seed to use. + * The seed can be used to generate reproducible values. + * + * Refer to the `seed()` method for more information. + * + * Defaults to a random seed. + */ + seed?: number; }) { - super({ randomizer: options.randomizer }); + super({ randomizer: options.randomizer, seed: options.seed }); let { locale } = options; diff --git a/src/internal/seed.ts b/src/internal/seed.ts new file mode 100644 index 00000000000..3ff484b2768 --- /dev/null +++ b/src/internal/seed.ts @@ -0,0 +1,8 @@ +/** + * Generates a random seed. + * + * @internal + */ +export function randomSeed(): number { + return Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER); +} diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 743df923570..1371e68a773 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -1,3 +1,4 @@ +import { randomSeed } from './internal/seed'; import { DatatypeModule } from './modules/datatype'; import { SimpleDateModule } from './modules/date'; import { SimpleHelpersModule } from './modules/helpers'; @@ -97,6 +98,10 @@ export class SimpleFaker { * Specify this only if you want to use it to achieve a specific goal, * such as sharing the same random generator with other instances/tools. * Defaults to faker's Mersenne Twister based pseudo random number generator. + * @param options.seed The initial seed to use. + * The seed can be used to generate reproducible values. + * Refer to the `seed()` method for more information. + * Defaults to a random seed. * * @example * import { SimpleFaker } from '@faker-js/faker'; @@ -120,11 +125,25 @@ export class SimpleFaker { * @default generateMersenne53Randomizer() */ randomizer?: Randomizer; + + /** + * The initial seed to use. + * The seed can be used to generate reproducible values. + * + * Refer to the `seed()` method for more information. + * + * Defaults to a random seed. + */ + seed?: number; } = {} ) { - const { randomizer = generateMersenne53Randomizer() } = options; + const { randomizer, seed } = options; + + if (randomizer != null && seed != null) { + randomizer.seed(seed); + } - this._randomizer = randomizer; + this._randomizer = randomizer ?? generateMersenne53Randomizer(seed); } /** @@ -247,9 +266,7 @@ export class SimpleFaker { * @since 6.0.0 */ seed(seed?: number | number[]): number | number[]; - seed( - seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) - ): number | number[] { + seed(seed: number | number[] = randomSeed()): number | number[] { this._randomizer.seed(seed); return seed; diff --git a/src/utils/mersenne.ts b/src/utils/mersenne.ts index 315335abfcc..585f84b5454 100644 --- a/src/utils/mersenne.ts +++ b/src/utils/mersenne.ts @@ -1,10 +1,13 @@ import { MersenneTwister19937 } from '../internal/mersenne'; +import { randomSeed } from '../internal/seed'; import type { Randomizer } from '../randomizer'; /** * Generates a MersenneTwister19937 randomizer with 32 bits of precision. * This is the default randomizer used by faker prior to v9.0. * + * @param seed The initial seed to use. Defaults to a random number. + * * @example * import { de, en, generateMersenne32Randomizer, Faker } from '@faker-js/faker'; * @@ -16,10 +19,12 @@ import type { Randomizer } from '../randomizer'; * * @since 8.2.0 */ -export function generateMersenne32Randomizer(): Randomizer { +export function generateMersenne32Randomizer( + seed: number = randomSeed() +): Randomizer { const twister = new MersenneTwister19937(); - twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + twister.initGenrand(seed); return { next(): number { @@ -39,6 +44,8 @@ export function generateMersenne32Randomizer(): Randomizer { * Generates a MersenneTwister19937 randomizer with 53 bits of precision. * This is the default randomizer used by faker starting with v9.0. * + * @param seed The initial seed to use. Defaults to a random number. + * * @example * import { de, en, generateMersenne53Randomizer, Faker } from '@faker-js/faker'; * @@ -50,10 +57,12 @@ export function generateMersenne32Randomizer(): Randomizer { * * @since 9.0.0 */ -export function generateMersenne53Randomizer(): Randomizer { +export function generateMersenne53Randomizer( + seed: number = randomSeed() +): Randomizer { const twister = new MersenneTwister19937(); - twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + twister.initGenrand(seed); return { next(): number { diff --git a/test/faker.spec.ts b/test/faker.spec.ts index 6d928d63652..dba19421912 100644 --- a/test/faker.spec.ts +++ b/test/faker.spec.ts @@ -1,18 +1,10 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { Faker, faker } from '../src'; +import { Faker, faker, generateMersenne32Randomizer } from '../src'; import { FakerError } from '../src/errors/faker-error'; import { keys } from '../src/internal/keys'; describe('faker', () => { - it('should throw error if no locales passed', () => { - expect(() => new Faker({ locale: [] })).toThrow( - new FakerError( - 'The locale option must contain at least one locale definition.' - ) - ); - }); - it('should not log anything on startup', async () => { const spies: MockInstance[] = keys(console) .filter((key) => typeof console[key] === 'function') @@ -69,19 +61,70 @@ describe('faker', () => { }); }); - describe('randomizer', () => { - it('should be possible to provide a custom Randomizer', () => { - const customFaker = new Faker({ - locale: {}, - randomizer: { - next: () => 0, - seed: () => void 0, - }, + describe('constructor()', () => { + describe('locale', () => { + it('should throw error if no locales passed', () => { + expect(() => new Faker({ locale: [] })).toThrow( + new FakerError( + 'The locale option must contain at least one locale definition.' + ) + ); + }); + }); + + describe('randomizer', () => { + it('should be possible to provide a custom Randomizer', () => { + const customFaker = new Faker({ + locale: {}, + randomizer: { + next: () => 0, + seed: () => void 0, + }, + }); + + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + }); + }); + + describe('seed', () => { + it('should be possible to provide an initial seed', () => { + const customFaker = new Faker({ + locale: {}, + seed: 12345, + }); + + expect(customFaker.number.int()).toBe(8373237378417847); + expect(customFaker.number.int()).toBe(2849657659447330); + expect(customFaker.number.int()).toBe(1656593383470774); + + customFaker.seed(12345); + + expect(customFaker.number.int()).toBe(8373237378417847); + expect(customFaker.number.int()).toBe(2849657659447330); + expect(customFaker.number.int()).toBe(1656593383470774); }); + }); - expect(customFaker.number.int()).toBe(0); - expect(customFaker.number.int()).toBe(0); - expect(customFaker.number.int()).toBe(0); + describe('randomizer+seed', () => { + it('should take apply both the randomizer and seed', () => { + const customFaker = new Faker({ + locale: {}, + randomizer: generateMersenne32Randomizer(67890), + seed: 12345, + }); + + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); + + customFaker.seed(12345); // Retry with the expected seed + + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); + }); }); }); diff --git a/test/internal/seed.spec.ts b/test/internal/seed.spec.ts new file mode 100644 index 00000000000..c3503524c8d --- /dev/null +++ b/test/internal/seed.spec.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; +import { randomSeed } from '../../src/internal/seed'; + +describe('seed', () => { + it('should generate a random seed', () => { + const actual = randomSeed(); + + expect(actual).toBeTypeOf('number'); + expect(actual).not.toBe(randomSeed()); + }); +}); diff --git a/test/simple-faker.spec.ts b/test/simple-faker.spec.ts index aa6f00c1892..02c860a50ee 100644 --- a/test/simple-faker.spec.ts +++ b/test/simple-faker.spec.ts @@ -1,6 +1,6 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { SimpleFaker, simpleFaker } from '../src'; +import { generateMersenne32Randomizer, SimpleFaker, simpleFaker } from '../src'; import { keys } from '../src/internal/keys'; describe('simpleFaker', () => { @@ -20,6 +20,60 @@ describe('simpleFaker', () => { } }); + describe('constructor()', () => { + describe('randomizer', () => { + it('should be possible to provide a custom Randomizer', () => { + const customFaker = new SimpleFaker({ + randomizer: { + next: () => 0, + seed: () => void 0, + }, + }); + + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + }); + }); + + describe('seed', () => { + it('should be possible to provide an initial seed', () => { + const customFaker = new SimpleFaker({ + seed: 12345, + }); + + expect(customFaker.number.int()).toBe(8373237378417847); + expect(customFaker.number.int()).toBe(2849657659447330); + expect(customFaker.number.int()).toBe(1656593383470774); + + customFaker.seed(12345); // Retry with the expected seed + + expect(customFaker.number.int()).toBe(8373237378417847); + expect(customFaker.number.int()).toBe(2849657659447330); + expect(customFaker.number.int()).toBe(1656593383470774); + }); + }); + + describe('randomizer+seed', () => { + it('should take apply both the randomizer and seed', () => { + const customFaker = new SimpleFaker({ + randomizer: generateMersenne32Randomizer(67890), + seed: 12345, + }); + + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); + + customFaker.seed(12345); // Retry with the expected seed + + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); + }); + }); + }); + // This is only here for coverage // The actual test is in mersenne.spec.ts describe('seed()', () => {