faker
faker copied to clipboard
Feat: Instantiate standalone Faker instance, import locales (or extend for custom subclass)
Goal:
Create a subclass that extends the Faker class so proprietary fake-data generators can be used side by side with faker's built-in ones.
Problem:
There seems to be a problem with the bound methods getting bound to undefined instead of to the instance. This is likely due to the empty locales option:
- I didn't see any way to import all locales like Faker's own code does, or even an individual locale (see code example for my attempts)
- The
Fakerconstructor initializeslocaletoen, but seems to use an empty object as the default forlocales.
Proposed solution(s):
- Set the default
localesinFakerconstructor to at least include theenlocale so the class can be used vianew Faker() - Throw an error if
new Faker(...)is instantiated with invalid options - Fix whatever is preventing the locales from being imported, so users can instantiate their own
Fakerinstances the "long" way.
Demo and other commentary here:
https://stackblitz.com/edit/faker-js-demo-q1dwn6?devtoolsheight=33&file=index.ts
The code is also below, in case that demo gets corrupted:
import { Faker } from '@faker-js/faker';
// I would expect at least one these work:
// import allLocales from '@faker-js/faker/locales';
// import { locales } from '@faker-js/faker/locales';
// import en from '@faker-js/faker/locales/en';
// import { en } from '@faker-js/faker/locales/en';
// This code shows how the bind is failing, resulting in
// "Error: Cannot read properties of undefined (reading 'internet')"
const baseFaker = new Faker(/* {locales: {en}} */);
console.log('baseFaker.internet.email: ', baseFaker.internet.email);
console.log(baseFaker.internet.email());
// Below here is the code I'm actually trying to write.
// I don't think it's relevant to the bug, but I've left
// it here as an example of the end goal and a possible
// solution.
export class MyFake extends Faker {
readonly customFake = new CustomFake(this);
}
export class CustomFake {
private readonly fake: MyFake;
constructor(fake: MyFake) {
this.fake = fake;
}
/* Side note: this syntax seems a lot easier than the
* bind() methods faker currently uses. */
public someFakeThing = () => {
return 'fake data';
};
}
const myfaker = new MyFake(/* {locales: {en}} */);
// confirm this works
console.log(myfaker.customFake.someFakeThing());
// some debug logs to show values "exist"
// console.log("myfaker: ", myfaker);
// console.log("myfaker.internet: ", myfaker.internet);
console.log('myfaker.internet.email: ', myfaker.internet.email);
// but this fails, presumably because email() is not bound
console.log(myfaker.internet.email());
And for posterity, here is a workaround (ugly, but better than nothing):
https://stackblitz.com/edit/faker-js-demo-5j7rsf?devtoolsheight=33&file=index.ts
import { faker, Faker } from '@faker-js/faker';
export class MyFake extends Faker {
// TODO: make this readonly after https://github.com/faker-js/faker/issues/448
customFake = new CustomFake(this);
}
export class CustomFake {
private readonly fake: MyFake;
constructor(fake: MyFake) {
this.fake = fake;
}
public someFakeThing = () => {
return "fake data";
}
}
// @ts-ignore workaround for https://github.com/faker-js/faker/issues/448
const fake: MyFake = faker;
fake.customFake = new CustomFake(fake);
console.log(fake.customFake.someFakeThing());
console.log(fake.internet.email());
Edit: this workaround can be made to work with ts 3.9 by editing one line to say const fake: MyFake & Faker = faker
I will try to investigate in next few days into this.
Currently I assume it's especially for the esm version, cause we use bundling and only specific entryPoints for this
https://github.com/faker-js/faker/blob/f1884becbc108c3ddbad952d879cec0fc5196e08/scripts/bundle.ts#L27-L34
Also we currently do not export single locales one by one.
We also need to export locales from https://github.com/faker-js/faker/blob/main/src/locales/index.ts and add an entryPoint for these files.
@ex-nerd I tried like two hours now and I cannot find a way to archive what you want right now https://github.com/faker-js/faker/pull/455#issuecomment-1032984642
One of the main problems is that we currently want to support cjs and esm
Could you provide a minimal reproduction of a working GitHub repository that works with faker v5.5.3?
If this never worked in faker <=5.5.3 then we will try to tackle this feature request in v7 or v8 or later
Could you provide a minimal reproduction of a working GitHub repository that works with
fakerv5.5.3? If this never worked in faker<=5.5.3then we will try to tackle this feature request inv7orv8or later
v5 typescript support was abysmal (namespace, not class), so as far as I know this definitely wouldn't be possible with that version (though my background is pretty much every language except javascript/typescript so I'm also not the best one to know).
The workaround in my previous comment isn't ideal but seems to work for now.
I was also looking for a way to multiple individual faker instances and ran into the same locale problem. This example code
import { Faker } from '@faker-js/faker';
const fakerA = new Faker();
const fakerB = new Faker();
fakerA.seed(100);
fakerB.seed(100);
console.log(fakerA.name.firstName(), fakerA.name.firstName()); // Karianne Marcus
console.log(fakerB.name.firstName(), fakerB.name.firstName()); // Karianne Marcus
throws with this.locales[this.locale] is undefined.
I managed to work around this by using the locales from the default faker export:
import { Faker, faker } from '@faker-js/faker';
const fakerA = new Faker({locales: faker.locales});
const fakerB = new Faker({locales: faker.locales});
fakerA.seed(100);
fakerB.seed(100);
console.log(fakerA.name.firstName(), fakerA.name.firstName()); // Karianne Marcus
console.log(fakerB.name.firstName(), fakerB.name.firstName()); // Karianne Marcus
How about adding a fork() method?
faker.fork will be implemented in #1499.
Additionally, custom faker instances should be easier to create in v8. Checkout https://github.com/faker-js/faker/releases/tag/v8.0.0-beta.0.
Team Notice
The top code example works in v8. Updated the code below to make it executable
import { Faker, FakerOptions, en } from "@faker-js/faker";
export class MyFake extends Faker {
constructor(options: FakerOptions) {
super(options);
}
readonly customFake = new CustomFake(this);
}
export class CustomFake {
private readonly fake: MyFake;
constructor(fake: MyFake) {
this.fake = fake;
}
/* Side note: this syntax seems a lot easier than the
* bind() methods faker currently uses. */
public someFakeThing = () => {
return "fake data";
};
}
const myfaker = new MyFake({ locale: [en] });
// confirm this works
console.log(myfaker.customFake.someFakeThing());
// some debug logs to show values "exist"
// console.log("myfaker: ", myfaker);
// console.log("myfaker.internet: ", myfaker.internet);
console.log("myfaker.internet.email: ", myfaker.internet.email);
// but this fails, presumably because email() is not bound
console.log(myfaker.internet.email());