SaddamHossain.Toolkit.FakeData 1.0.0

dotnet add package SaddamHossain.Toolkit.FakeData --version 1.0.0
                    
NuGet\Install-Package SaddamHossain.Toolkit.FakeData -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SaddamHossain.Toolkit.FakeData" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SaddamHossain.Toolkit.FakeData" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SaddamHossain.Toolkit.FakeData" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SaddamHossain.Toolkit.FakeData --version 1.0.0
                    
#r "nuget: SaddamHossain.Toolkit.FakeData, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SaddamHossain.Toolkit.FakeData@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SaddamHossain.Toolkit.FakeData&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SaddamHossain.Toolkit.FakeData&version=1.0.0
                    
Install as a Cake Tool

SaddamHossain.Toolkit.FakeData

NuGet Downloads Build License: MIT .NET

A modern, dependency-free, strongly-typed fake data generator for .NET — with deterministic seeding and first-class country providers.

Version 1.0.0 — the initial release. 197 public members across 19 types, 378 tests, zero dependencies.


Contents

Introduction · Features · Installation · Quick start · Architecture · API · Seed support · Country providers · Object generation · Validation · Performance · Requirements · Roadmap · Versioning · Contributing · Security · License


Introduction

Every project needs realistic test data, and every project ends up generating it badly: a static array of five names, a Random that reshuffles on every run, an address whose city and country have never met, a phone number that turns out to belong to somebody.

SaddamHossain.Toolkit.FakeData does that job properly. One entry point, three shapes for every generator, data that is internally consistent, and a seed that means the same thing on every machine you will ever run it on.

using SaddamHossain.Toolkit.FakeData;

// A single value
string name = Fake.Person.FullName();

// A collection
IReadOnlyList<string> names = Fake.Person.FullNames(100);

// A strongly-typed object
PersonModel person = Fake.Person.Generate();

// Many objects
IReadOnlyList<PersonModel> people = Fake.Person.Generate(100);

// Country-specific
IReadOnlyList<BangladeshPersonModel> bangladeshis = Fake.Country.Bangladesh.Generate(100);

// Reproducible
Fake.UseSeed(42);
IReadOnlyList<PersonModel> repeatable = Fake.Person.Generate(100);

Features

  • Zero dependencies. The package references no other NuGet package. Nothing enters your dependency graph but this assembly.
  • Multi-targeted for net8.0, net9.0 and net10.0.
  • Trim- and AOT-safe. Marked IsAotCompatible, so Native AOT and trimmed publishes stay warning-free. No unsafe, no dynamic, no regular expressions, and no reflection beyond the embedded-resource lookup that loads the datasets.
  • Genuinely deterministic. Fake.UseSeed(42) reproduces byte-identical data on any OS, any CPU architecture and any target framework — because the generator ships inside the package rather than delegating to Random, whose seeded algorithm may change between .NET versions.
  • Thread-safe with no locks. Seed state is per-thread, so parallel tests cannot consume each other's draws and no generator ever contends.
  • Internally consistent objects. A generated person's email is built from their name, their given name matches their gender, and a company's website and contact address share a domain derived from its own name.
  • Country providers that are actually correct. Fake.Country.Bangladesh produces addresses whose upazila really is inside its district, which really is inside its division.
  • Safe by construction. Phone numbers use the NANP fictional range, IPv4 addresses come from the RFC 5737 documentation blocks, IPv6 from RFC 3849, MAC addresses are locally administered, and SafeEmail() stays inside RFC 2606 reserved domains.
  • Datasets in JSON, not in C#. 21 embedded JSON files parsed with a hand-written Utf8JsonReader walker — no hardcoded arrays, no JsonSerializer, nothing for the trimmer to break.
  • Fully documented. Every public member ships XML documentation covering its parameters, return value and every exception it throws deliberately — enforced at build time, since CS1591 is an error here. Members whose usage is not obvious from the summary alone carry a worked <example>.
  • Source Link + symbols. Step straight into the source from your debugger.

Installation

dotnet add package SaddamHossain.Toolkit.FakeData

Or via the Package Manager Console:

Install-Package SaddamHossain.Toolkit.FakeData

Quick start

Everything lives in a single namespace, so one using makes the whole library discoverable through IntelliSense by typing Fake.:

using SaddamHossain.Toolkit.FakeData;

That is deliberate. The library is organised into folders internally, but splitting the namespace by feature would force you to import six namespaces to read one generated person — the same reasoning behind System.Linq and Microsoft.Extensions.*.

Architecture

Fake                          the single entry point
├── UseSeed / ResetSeed / CurrentSeed
├── Person      ─┐
├── Internet     │
├── Company      ├─ generic generators, locale-neutral
├── Address      │
├── Commerce    ─┘
└── Country
    └── Bangladesh            ICountryProvider<BangladeshPersonModel>

Models     PersonModel · CompanyModel · AddressModel · InternetModel
           CommerceProductModel · BangladeshPersonModel
Enums      Gender · CountryCode

Internally, six helpers do the work and none of them is public: SeedManager owns the per-thread generator, RandomProvider is the drawing surface every generator goes through, SelectionHelper turns a dataset plus a draw into one value or a batch, JsonDatasetReader parses the embedded JSON, DatasetLoader reads it out of the assembly, and CountryResolver maps a CountryCode onto its provider.

Every generator follows the same three shapes. That is the whole ergonomic argument:

Shape Example Returns
Single value Fake.Person.FullName() string
Collection Fake.Person.FullNames(100) IReadOnlyList<string>
Object Fake.Person.Generate() PersonModel
Objects Fake.Person.Generate(100) IReadOnlyList<PersonModel>

A test asserts this pairing across the whole API, so it cannot drift.

API

Fake.Person

Method Returns Summary
FirstName() / (Gender) string Given name
LastName() string Family name
FullName() / (Gender) string Given + family
Gender() Gender Which name list a person draws from
Email() string Derived from a generated name
Phone() string NANP fictional range
DateOfBirth() / (int, int) DateOnly 18–80 by default
Generate() PersonModel Complete, internally consistent

Plural forms: FirstNames, LastNames, FullNames, Genders, Emails, Phones, DatesOfBirth, Generate(int).

Fake.Internet

Method Returns Summary
UserName() string ada.lovelace
Email() / (string, string) string Fictional brand domain
SafeEmail() string RFC 2606 reserved domain
Domain() string northwind.dev
Url() string Absolute HTTPS
IPv4() string RFC 5737 documentation block
IPv6() string RFC 3849 2001:db8::/32
MacAddress() string Locally administered
Generate() InternetModel All of the above, consistent

Plural forms: UserNames, Emails, SafeEmails, Domains, Urls, IPv4Addresses, IPv6Addresses, MacAddresses, Generate(int).

Fake.Company

Method Returns Summary
Name() string Northwind Technologies
Industry() string Renewable Energy
Department() string Quality Assurance
JobTitle() string Senior Data Engineer
Generate() CompanyModel Website and email share the name's domain

Plural forms: Names, Industries, Departments, JobTitles, Generate(int).

Fake.Address

Method Returns Summary
Street() string 482 Maple Avenue
City() string City or town
State() string State, province or region
PostCode() string Five digits, no leading zero
Country() / CountryCode() string Name / ISO 3166-1 alpha-2
FullAddress() string One comma-separated line
Generate() AddressModel Country name and code always agree

Plural forms: Streets, Cities, States, PostCodes, Countries, CountryCodes, FullAddresses, Generate(int).

Generic addresses are locale-neutral, not country-accurate. A generated record may pair a city, a region and a country that have no relationship, and the post code follows no national scheme. This is a deliberate trade and it is worth stating plainly: one generic generator can be shallow everywhere or accurate nowhere. When the address must be genuinely correct, use a country provider.

Fake.Commerce

Method Returns Summary
Product() string Ergonomic Steel Chair
Category() string Home Improvement
Sku() string SKU-4827-XQ
Price() / (decimal, decimal) decimal Exact to two places
Currency() string ISO 4217 code
Generate() CommerceProductModel Code and symbol always agree

Plural forms: Products, Categories, Skus, Prices, Currencies, Generate(int).

Fake.Country

Member Returns Summary
Bangladesh BangladeshProvider The implemented provider
Supported IReadOnlyList<ICountryProvider> What ships today
Get(CountryCode) ICountryProvider Throws if not implemented
TryGet(CountryCode, out …) bool Non-throwing, flow-analysed
IsSupported(CountryCode) bool Feature check

Extension methods

Applying the seeded generator to your own collections:

Method Receiver Returns
PickRandom() IReadOnlyList<T> T
PickRandom(int) IReadOnlyList<T> IReadOnlyList<T>
Shuffle() IReadOnlyList<T> IReadOnlyList<T>

These bind to IReadOnlyList<T> rather than IEnumerable<T> on purpose. .NET 10 added Enumerable.Shuffle on IEnumerable<T>; declaring ours there too would make every call site with both namespaces in scope fail with CS0121: ambiguous call — on net10.0 only.

Seed support

Fake.UseSeed(seed) makes every subsequent generation on the calling thread reproducible.

Fake.UseSeed(12345);
string first = Fake.Person.FullName();

Fake.UseSeed(12345);
string second = Fake.Person.FullName();

// first == second — on Windows, Linux, macOS, x64, Arm64, net8.0, net9.0 and net10.0.

This guarantee is underwritten by a xoshiro256** generator implemented inside the package, seeded through SplitMix64. Random cannot underwrite it: the algorithm behind the seeded Random(int) constructor is explicitly documented as an implementation detail that may change between .NET versions, and a test fixture that silently reshuffles itself when the host upgrades its runtime is worse than no determinism at all.

The seed applies to the thread that set it. This is the one rule worth internalising:

Fake.UseSeed(42);

await Task.Run(() =>
{
    // This thread was never seeded — it generates unseeded data.
    Fake.Person.FullName();
});

Test runners execute collections in parallel, and a single shared generator would let two seeded tests consume each other's draws — producing a determinism feature that fails intermittently, which is the worst possible failure mode because it looks like flakiness somewhere else. Per-thread state makes a seed mean something under parallelism, and removes all locking from the hot path.

Fake.ResetSeed() returns the thread to unseeded generation; Fake.CurrentSeed reports the seed in force, or null.

Not a source of secrets. The generator is chosen for reproducibility, which is the exact opposite of unpredictability. Nothing produced by this library may be used as a password, token, key, or any identifier that needs to be unguessable.

Country providers

Fake.Country.Bangladesh is the first implemented provider.

Fake.Country.Bangladesh.Name();       // "Tanvir Rahman"
Fake.Country.Bangladesh.Mobile();     // "01712345678"
Fake.Country.Bangladesh.NID();        // "4829173065"
Fake.Country.Bangladesh.Division();   // "Khulna"
Fake.Country.Bangladesh.District();   // "Cox's Bazar"
Fake.Country.Bangladesh.Upazila();    // "Kaliakair"
Fake.Country.Bangladesh.PostCode();   // "4000"
Fake.Country.Bangladesh.Company();    // "Meghna Group Industries"
Fake.Country.Bangladesh.Address();    // AddressModel

The geography nests correctly. An address names a real upazila inside the real district that contains it, inside the real division that contains that:

Holding 199, Sarak 8, Gopibagh, Barhatta, Netrokona, Mymensingh 4310, Bangladesh
House 171, Sarak 32, Sonadanga, Debiganj, Panchagarh, Rangpur 3700, Bangladesh
House 212, Lane 19, Kalabagan, Tazumuddin, Bhola, Barishal 5100, Bangladesh

Three independent draws from three flat lists would produce "Teknaf, Rangpur, Sylhet", which is nonsense to anyone who lives there — and is exactly the kind of detail that makes a demo lose credibility. The dataset is stored as a nested hierarchy for this reason, and tests assert all 8 divisions, all 64 districts, and that every upazila maps to a real district.

Mobile numbers use real operator prefixes (013–019) so they pass the format validation most Bangladeshi systems apply. National IDs are structurally shaped like a Smart NID but carry no checksum and encode nothing — treat them as opaque test identifiers.

Adding countries without breaking changes

CountryCode names more countries than are implemented, on purpose:

Fake.Country.IsSupported(CountryCode.Bangladesh);   // true
Fake.Country.IsSupported(CountryCode.Japan);        // false — on the roadmap

if (Fake.Country.TryGet(CountryCode.Japan, out ICountryProvider? provider))
{
    Console.WriteLine(provider.Address());   // No null warning here.
}

That split lets you write code today that keeps compiling when a provider lands, and makes a feature check a method call rather than a version comparison. ICountryProvider holds only what is genuinely universal; anything country-specific lives on the concrete provider, which is what makes each new country purely additive.

// Write once, work for any country the library supports.
static void Seed(ICountryProvider country, int rows)
{
    foreach (AddressModel address in country.Addresses(rows))
    {
        Console.WriteLine($"{country.IsoCode}: {address}");
    }
}

Seed(Fake.Country.Bangladesh, 100);

Object generation

Generated objects are internally consistent — the record reads like one person, not eight unrelated draws:

PersonModel person = Fake.Person.Generate();

person.FullName;    // "Ada Lovelace"
person.Gender;      // Female — and "Ada" came from the matching name list
person.UserName;    // "ada.lovelace"
person.Email;       // "ada.lovelace@fabrikam.com" — the user name, on a generated domain
person.Address;     // AddressModel
person.Age;         // computed from DateOfBirth
person.AgeOn(new DateOnly(2030, 1, 1));

Every model is a sealed record, so value equality makes a determinism assertion a single line:

Fake.UseSeed(7);
IReadOnlyList<PersonModel> first = Fake.Person.Generate(50);

Fake.UseSeed(7);
IReadOnlyList<PersonModel> second = Fake.Person.Generate(50);

Assert.Equal(first, second);   // compares every field of all fifty

ToString() is overridden on every model to render something you would actually want in a log or a CSV, rather than the record default:

Fake.Person.Generate().ToString();     // "Ada Lovelace <ada.lovelace@fabrikam.com>"
Fake.Address.Generate().ToString();    // "482 Maple Avenue, Fairhaven, Northern Province 48203, Portugal"
Fake.Commerce.Generate().ToString();   // "Ergonomic Steel Chair — £249.99"

Age and leap days

Age is computed from DateOfBirth each time it is read, so it moves with the calendar — pin DateOfBirth in tests, or use AgeOn(date).

A 29 February birth has its anniversary on 1 March in a common year. DateOnly.AddYears would clamp it to 28 February, which would make someone born on 29 February 2000 turn one a day before the month they were born in had ended:

// born 2000-02-29
person.AgeOn(new DateOnly(2001, 2, 28));   // 0
person.AgeOn(new DateOnly(2001, 3, 1));    // 1
person.AgeOn(new DateOnly(2004, 2, 29));   // 4
person.AgeOn(new DateOnly(2100, 2, 28));   // 99 — 2100 is not a leap year

Validation

Every generator validates its arguments:

Fake.Person.FirstNames(0);            // ArgumentOutOfRangeException — count must be positive
Fake.Person.FirstNames(-1);           // ArgumentOutOfRangeException
Fake.Person.DateOfBirth(50, 20);      // ArgumentOutOfRangeException — inverted range
Fake.Commerce.Price(100m, 10m);       // ArgumentOutOfRangeException — inverted range
Fake.Internet.Email(null!, "Smith");  // ArgumentNullException
Fake.Country.Get(CountryCode.Japan);  // NotSupportedException, naming what IS supported

Zero is rejected rather than returning an empty list: "any positive count" is the documented contract, and a silent empty result is a harder bug to find than an exception.

Collections draw with replacement, so count may exceed the dataset size and duplicates are possible. That is what makes Fake.Person.FirstNames(1000) meaningful against a few hundred names.

Performance

Run the benchmarks yourself:

cd benchmarks/SaddamHossain.Toolkit.FakeData.Benchmarks
dotnet run -c Release -f net10.0

Five suites are included: single generation, bulk generation (100 and 1000), seeded generation, dataset loading, and country providers. All carry [MemoryDiagnoser], because the claims worth checking are about allocation:

  • A draw from a dataset allocates nothing — the string already exists in the cached array.
  • Bulk generators allocate their array once at exactly the requested size, so cost scales linearly.
  • Seeded and unseeded generation run the same code; determinism costs nothing measurable.
  • Dataset parsing is a once per process cost, paid lazily, and only for datasets you touch.

Requirements

Runtime .NET 8.0, .NET 9.0 or .NET 10.0
SDK (to build) .NET 10.0 SDK or later
Language C# 12 or later
OS Any platform supported by .NET — no OS-specific code

Roadmap

Public API changes follow Semantic Versioning — no breaking change without a major version bump.

Candidate Notes
More country providers CountryCode already names United States, Canada, United Kingdom, India, Japan and Australia. Each needs real names, identifiers, address hierarchy and phone formats — a provider ships when its data is genuinely right, not merely plausible.
Fake.Lorem Sentences and paragraphs. Straightforward, but it needs a corpus decision first.
Fake.Finance IBAN, credit-card numbers with valid Luhn checksums, account numbers.
Fake.Date Ranges, business days, recent/soon helpers.
Custom datasets Supplying your own JSON in place of the embedded set. Wanted, but the API has to stay AOT-safe.

Requests and use cases are welcome on the issue tracker — a generator that solves a real problem you have is a much better argument than one that rounds out a table.

Versioning

This package follows Semantic Versioning 2.0.0.

Change Version bump Example
Breaking change to the public API Major2.0.0 A method removed, renamed, or its signature or documented behaviour changed
New API, fully backward compatible Minor1.1.0 A new generator, a new overload, a new country provider
Bug fix with no API change Patch1.0.1 A correctness fix, a performance improvement

Three guarantees come with that:

  • AssemblyVersion moves only on a major release. Code compiled against 1.0.0 keeps loading 1.4.2 with no binding redirect. A unit test enforces this, so it cannot drift by accident.
  • The public API surface is pinned by a test. Any addition, removal or signature change fails the build with a readable diff, which makes every contract change a deliberate, reviewed decision.
  • Seeded output is part of the contract. Pinned-value tests assert exact strings for fixed seeds, so a change to the generator, the draw order, or any dataset fails the build rather than silently invalidating every fixture you have recorded. Such a change is treated as breaking.

Pre-releases use the standard suffix form — 1.1.0-preview.1 — and are never promoted to stable without a version bump.

Contributing

Contributions are welcome, and adding a country provider is the most valuable one available — the architecture exists for it. CONTRIBUTING.md covers building, the coding and testing standards, what counts as a breaking change, and a step-by-step guide to adding a country.

The short version:

dotnet build -c Release          # must produce zero warnings — warnings are errors here
dotnet test  -c Release          # 378 tests × 3 target frameworks
dotnet format --verify-no-changes

Open an issue before writing code: design discussion happens before implementation, not during review. Everyone taking part is expected to follow the Code of Conduct.

Security

Report vulnerabilities privately through GitHub Security Advisories, never as a public issue. SECURITY.md sets out what is in scope, the response times to expect, and the hardening a reviewer can verify before adopting the package.

One point bears repeating here: nothing this library produces may be used as a secret. The generator is chosen for reproducibility, which is the exact opposite of unpredictability. For passwords, tokens and keys, use System.Security.Cryptography.RandomNumberGenerator.

📦 NuGet nuget.org/packages/SaddamHossain.Toolkit.FakeData
💻 Source github.com/saddamhossain/SaddamHossain.Toolkit.FakeData
🐛 Issues Report a bug or request a feature
💬 Discussions Ask a question
📋 Changelog CHANGELOG.md
🤝 Contributing CONTRIBUTING.md
🔒 Security SECURITY.md
🌐 Website saddamhossain.net

License

Licensed under the MIT License — free for commercial and personal use, with no attribution required beyond retaining the notice. A copy of the licence ships inside the NuGet package itself.

Copyright © 2026 Md. Saddam Hossain


Built by Md. Saddam Hossain. If this package saves you time, a ⭐ on GitHub is appreciated.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 60 8/7/2026