rand 5.0.1
rand: ^5.0.1 copied to clipboard
Seedable test-data generator: names, emails, IPs, UUIDv4/v7, ULID, hex, slugs, digits, semver, lorem, CSS colors, geo, weighted samples, CSPRNG tokens. Pure Dart. Agent skill.
rand #
rand generates realistic random values for Dart — names, emails, dates, IP
addresses, UUIDs, lorem text, CSS colors, weighted picks — one static call each.
Seed it, and every call except the cryptographic ones returns the same values on
the next run. Pure Dart, all six platforms, no Flutter dependency.
Why #
Test data usually ends up as fixed strings ('John Doe', 'test@test.com')
that exercise one path forever, or as a small Random helper that every project
writes again. Varied data covers more cases, but a random test that fails once
is hard to reproduce. rand gives you:
- The helper, already written.
Rand.email(),Rand.dateTime(min: a, max: b),Rand.sample(items, count: 10, weights: w)instead of code you maintain. - Failures you can replay. Give each test a random seed and print it on
failure;
Rand.seed(n)brings back exactly the same data. - A strict, predictable API. Ranges are half-open like
Random.nextInt, default emails use reserved domains (example.com,*.test), never real ones, and invalid arguments throw instead of being quietly adjusted.
Quick start #
If only your tests use rand, add it as a dev dependency:
dart pub add dev:rand
App code uses it too (demo data, prototypes)? Run dart pub add rand instead.
A test that gets new data on every run and can still replay a failure:
import 'dart:convert';
import 'package:rand/rand.dart';
import 'package:test/test.dart';
void main() {
// Fresh data on every run; a failing test prints the seed that replays it.
setUp(() {
Rand.seed();
printOnFailure('Replay with Rand.seed(${Rand.currentSeed})');
});
test('a user survives a JSON round trip', () {
final user = {'name': Rand.fullName(), 'email': Rand.email()};
expect(jsonDecode(jsonEncode(user)), user);
});
}
Fixed values instead? Seed with a constant. These are the values Rand.seed(42)
produces:
Rand.seed(42);
Rand.fullName(); // 'Sophia Tran'
Rand.email(); // 'david88@example.com'
Rand.ipv4(); // '143.61.48.184'
Rand.password(); // 12 chars, CSPRNG — not seeded
Rand.color(); // CssColors.darkKhaki
Rand.dateTime(); // 1972-02-25 21:42:51.597617Z
// 10 draws, 'common' 100 times as likely as 'rare'
Rand.sample(['rare', 'common'], count: 10, weights: [1, 100]);
When to use it #
- Test fixtures and seed data.
- Mocking API responses.
- Demos, prototypes, throwaway tokens.
- Game prototyping (loot-box weighting, dice, color palettes).
For composing structured fixtures (User, Address, Order, paginated responses,
chat history) on top of these primitives, see
example/recipes.dart.
When not to use it #
- Not faker.
randreturns flat values. Compose typed entities yourself:User(name: Rand.fullName(), city: Rand.city()). - Not a crypto library.
Rand.password()andRand.nonce()use the platform CSPRNG, but the package's stability contract is "test data," not production secrets. Defaults may shift across major versions. - Not a UUID library.
Rand.uuidV4(),uuidV7()andulid()are seedable test IDs drawn from the non-cryptographic RNG. For production IDs usepackage:uuid.
API at a glance #
Everything below is a static member of Rand, apart from the RandGen class
and the CssColors enum. A RandGen carries the same generators over your own
Random, minus the cryptographic ones and the seeding controls.
| Area | API |
|---|---|
| Seeding | seed, currentSeed, withSeed, useRng, RandGen |
| Numbers | integer, float, latitude, longitude, charCode |
| Probability | boolean, nullable |
| Text | word, words, sentence, paragraph, article |
| Identity and geo | firstName, lastName, fullName, alias, city, geoPoint |
| Networking | email, ipv4, ipv6, mac, hex, semver, digits, slug |
| IDs | uuidV4, uuidV7, ulid |
| Time | dateTime, duration |
| Collections | element, enumValue, subset, shuffled, mapKey, mapValue, mapEntry |
| Weighted sampling | sample |
| Colors | color, colorDark, colorLight, CssColors |
| Cryptographic tokens | bytes, nonce, password, base64, secureCharCode, base62Alphabet (Rand only) |
Seeding and reproducibility #
rand uses two RNGs. Every non-cryptographic method draws from a replaceable
Random; the cryptographic ones always use Random.secure().
import 'dart:math';
void main() {
Rand.useRng(Random(42)); // any Random instance
Rand.seed(42); // useRng(Random(42)), and Rand.currentSeed == 42
Rand.useRng(Random.secure()); // global swap to a CSPRNG; clears currentSeed
// One callback with its own seed; the global stream is untouched.
final user = Rand.withSeed(7, () => (Rand.fullName(), Rand.email()));
// An independent stream with the same non-crypto API.
final gen = RandGen(Random(1));
final name = gen.fullName();
// One secure draw; the global RNG and currentSeed stay untouched.
final picks = RandGen(Random.secure()).sample(['a', 'b', 'c'], count: 2);
}
useRng and seed mutate the global non-cryptographic RNG. withSeed scopes a
seed to one callback: it is zone-based, so it follows awaits, nests (the inner
scope wins), and never touches the global. RandGen is the non-cryptographic
API as an instance over your own Random — inject it where explicit wiring
beats a global; independent instances are independent streams.
RandGen.seeded(1) is shorthand for RandGen(Random(1)) without importing
dart:math. Cryptographic methods (password, nonce, bytes, base64,
secureCharCode) live only on Rand, always use Random.secure(), and ignore
all of these.
Replay a failing test #
Pick a random seed per test and print it only when the test fails:
import 'package:test/test.dart';
setUp(() {
Rand.seed(); // random seed in [0, 2^32)
printOnFailure('Rand seed: ${Rand.currentSeed}'); // replay with Rand.seed(n)
});
Seed in setUp, not setUpAll: each test starts from the same state whatever
ran before it (--name, --test-randomize-ordering-seed).
Ranges and counts #
Every range is [min, max) half-open, like Random.nextInt and nextDouble:
integer, float, dateTime, duration. An empty range (min >= max) throws
ArgumentError, like nextInt(0).
Counts accept 0 and return an empty result (words, sentence, paragraph,
article, bytes, subset, sample). Single-token generators — nonce,
hex, digits, base64, slug — need a length of at least 1.
Numbers #
Rand.integer(); // [0, 2^31)
Rand.integer(min: 50, max: 100); // 50..99
Rand.float(); // [0.0, 1.0), like nextDouble
Rand.float(min: 10, max: 20); // [10.0, 20.0)
Rand.latitude(); // -90..90, 5 decimal places
Rand.longitude(precision: 3); // -180..180, 3 decimal places
Rand.charCode(); // base62 code point (int)
integer(max: list.length) is always a valid index of a non-empty list (or just
call Rand.element(list)). float's default max is 1, so float(min: 5)
throws — pass a max.
Probability #
Rand.boolean(); // 50% true
Rand.boolean(trueChance: 90); // 90% true
Rand.boolean(trueChance: 99.9); // 99.9% true
Rand.nullable('value'); // 50% null, useful in fixtures
Rand.nullable('value', nullChance: 90); // 90% null
Text #
Lorem ipsum from a built-in corpus.
Rand.word(); // 'lorem'
Rand.words(count: 5); // 'amet consectetur adipiscing elit sed'
Rand.words(count: 3, separator: '-');
Rand.sentence(); // 'Lorem ipsum dolor sit amet.'
Rand.sentence(count: 3); // 3 unique sentences joined by ' '
Rand.paragraph(count: 3); // 3 sentences joined by ' '
Rand.article(count: 5); // 5 paragraphs separated by '\n\n'
words(count: N) and sentence(count: N) use subset — the same word/sentence
never repeats in one call (corpora: 167 unique words, 856 sentences). For
repeats, call word() / sentence() N times yourself, or use
paragraph(count: N).
Identity and geo #
Rand.firstName(); // 'Olivia'
Rand.lastName(); // 'Thompson'
Rand.fullName(); // 'James Michael Wilson' — 0..2 weighted middle names
Rand.alias(); // 'Achilles'
Rand.city(); // 'Boston'
Rand.geoPoint(); // (lat: 42.36011, lng: -71.05891) — named record
Corpora are US/English-leaning. For locale-aware data, reach for
package:faker.
Geo points are uniform in degrees, not over the sphere's area: |lat| > 60 is
~33% of draws versus ~13% of the Earth's surface, and rounding to precision
gives ±90 / ±180 half the weight of other values.
Networking #
Rand.email(); // 'olivia42@example.com'
Rand.email(domain: 'mycompany.io'); // 'james7@mycompany.io'
Rand.ipv4(); // '203.0.113.42'
Rand.ipv6(); // '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
Rand.mac(); // '3a:5f:9c:8e:2d:71'
Rand.mac(separator: '-'); // '3a-5f-9c-8e-2d-71'
Rand.hex(length: 40); // git-SHA-shaped opaque hex
Rand.semver(); // '3.7.42'
Rand.digits(); // '047215'
Rand.slug(); // 'lorem-ipsum-dolor'
email picks its default domain from a built-in list of RFC 2606 reserved names
(example.com, example.org, example.net and *.test domains) — safe to
ship in fixtures without collision risk. ipv4 is not filtered for reserved
ranges; compose your own filter if you need only routable addresses. ipv6 is
returned in full form (no :: collapse) so fixture length stays stable. hex
is general-purpose lowercase hex — git SHAs, ETags, opaque content hashes. All
of these use the non-secure RNG and are reproducible under Rand.seed.
IDs #
final instant = DateTime.utc(2026, 10, 3, 12, 34, 56, 789);
Rand.uuidV4(); // '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'
Rand.uuidV7(time: instant); // '01a101c2-a895-7…'
Rand.uuidV7(time: DateTime.now()); // live clock
Rand.ulid(time: instant); // '01M40W5A4N…', 26 chars
All three draw from the non-cryptographic RNG, so they reproduce under seed,
withSeed and RandGen. uuidV7 and ulid take a required time: a fixed
instant keeps the whole ID stable for snapshot tests, DateTime.now() gives a
live clock. v7 and ULID sort by time across milliseconds but are not monotonic
within one; a time before 1970 or past year 10889 throws. Production IDs →
package:uuid.
Time #
Rand.dateTime(); // [1970-01-01, 2038-01-01) UTC
Rand.dateTime(min: DateTime.utc(2020), max: DateTime.utc(2025));
Rand.duration(max: const Duration(days: 30)); // 0 to 30 days
Rand.duration(min: const Duration(days: 1), max: const Duration(days: 30));
dateTime and duration are [min, max) half-open and throw ArgumentError
on an empty or reversed range — including dateTime(min: t) with t at or
after 2038-01-01 and no max. dateTime always returns UTC.
Collections #
enum Status { active, suspended, deleted }
final fruits = ['apple', 'orange', 'lemon', 'grape', 'kiwi'];
final scores = {'Alice': 95, 'Bob': 87};
Rand.element(fruits); // 'orange'
Rand.enumValue(Status.values); // Status.suspended — typed enum draw
Rand.subset({1, 2, 3, 4, 5}, count: 3); // {2, 5, 1} — unique elements
Rand.shuffled(fruits); // ['kiwi', 'grape', 'apple', ...] — copy
Rand.mapKey(scores); // 'Bob'
Rand.mapValue(scores); // 95
Rand.mapEntry(scores); // MapEntry('Alice', 95)
Pick the right call:
| Need | Use |
|---|---|
| One element | element(iterable) |
| One enum member | enumValue(MyEnum.values) |
| N unique elements | subset(set, count: N) |
| N elements, repeats okay | sample(list, count: N) |
| N elements with weighted frequency | sample(..., weights: [...]) |
| Whole list, reordered | shuffled(list) |
| One key / value / entry of a Map | mapKey / mapValue / mapEntry |
subset requires Set<T> — dedupe explicitly with .toSet() if your source
has duplicates.
Weighted sampling #
sample draws with replacement, optionally weighted.
// Loot box: weights are relative, so 1 : 10 : 100 is ~0.9% / ~9% / ~90%
final loot = Rand.sample(
['Legendary', 'Rare', 'Common'],
count: 100,
weights: [1, 10, 100],
);
// Equal probability — drop weights
final dice = Rand.sample([1, 2, 3, 4, 5, 6], count: 10);
weights.length must equal from.length. An empty from throws StateError
unless count is 0. For a secure sample, draw from your own generator —
RandGen(Random.secure()).sample(...) — so the global RNG and
Rand.currentSeed stay untouched.
Colors #
final c = Rand.color(); // CssColors.coral
final name = c.name; // 'coral'
final argb = c.argb; // 0xFFFF7F50 — ARGB packed int
final isDark = c.isDark; // computed via YIQ luminance
Rand.colorDark(); // dark colors only — good for light backgrounds
Rand.colorLight(); // light colors only — good for dark backgrounds
148 CSS named variants. The argb int is Flutter-friendly: Color(c.argb)
works without conversion. isDark is a CssColors getter — computed from
argb via the YIQ luminance formula, not stored on the enum. Use it (or your
own contrast helper, e.g. fluiver's
Color.contrastText) to pick a foreground.
Cryptographic tokens #
Rand.bytes(length: 32); // Uint8List, always Random.secure()
Rand.nonce(); // 16-char base62, always Random.secure()
Rand.nonce(length: 32);
Rand.password(); // 12-char mixed-charset, always Random.secure()
Rand.password(length: 20, symbols: false);
Rand.base64(); // 16 bytes encoded, always Random.secure()
Rand.secureCharCode(); // base62 code point, always Random.secure()
// The 62 characters nonce, secureCharCode and charCode draw from
const alphabet = Rand.base62Alphabet; // 0-9, then A-Z, then a-z
Three rules:
- Seed doesn't reach secure methods.
Rand.seed(42); Rand.password()is fresh CSPRNG output every call. For reproducible token tests, generate them from your ownRandominstance. - Don't ship rand-generated tokens as production secrets. The RNG is
correct; the package contract is "test data." Defaults can shift across major
versions. Use
package:cryptographyor your platform's keystore for real secrets. bytesandnonceare always secure post-v4 — thesecure:parameter is gone.
Crypto methods throw under dart test -p node: dart2js has no secure RNG there.
Every other method works.
Pitfalls #
| ❌ | ✅ |
|---|---|
Rand.integer(min: 1, max: 6) for a die roll — max is exclusive, never 6 |
Rand.integer(min: 1, max: 7) |
Rand.float(min: 5) — default max is 1, so it throws |
Pass both bounds: Rand.float(min: 5, max: 10) |
Rand.integer(max: list.length) on a list that may be empty — [0, 0) is empty and throws |
Guard with list.isNotEmpty, or call Rand.element(list) |
Rand.seed(42); Rand.password() — seed never reaches CSPRNG |
Build deterministic tokens from your own Random(42) |
Rand.subset([1, 2, 2], count: 2) — compile error: subset takes a Set<T> |
Pass a set: Rand.subset({1, 2}, count: 2), or dedupe with .toSet() |
Shipping Rand.nonce / Rand.password as production secrets |
Use package:cryptography or a platform keystore |
setUpAll(() => Rand.seed(42)) |
Use setUp — each test starts from the same state whatever ran before it (--name, --test-randomize-ordering-seed) |
list..shuffle() to "get a random order" without keeping the original |
Rand.shuffled(list) — non-mutating copy |
Agent skill #
This package ships an agent skill in skills/rand-test-data/. Install it into
your project's agent config with:
dart run skills@ get --package rand --all
Add --agent <name> (claude, cursor, codex, copilot, …) when no agent config is
detected.
Contributing #
See CONTRIBUTING.md.
License #
MIT — see LICENSE.