M7 is the milestone where Munchausen stops being a clever skeleton and starts producing data you'd actually believe. Eight dataset classes, a pile of English word tables, and the wiring that turns every "this should be a first name" decision from earlier into an actual "Anthony." It's the largest milestone by a wide margin, and it's the one where all those throw new NotImplementedException("bound in M7") IOUs from M5 and M6 finally come due.
There's a particular satisfaction in deleting placeholder code by replacing it with the real thing.
Curating fake reality
A lot of this milestone is taste expressed as data: which first names, cities, and product adjectives to include. While designing the datasets, I decided their public methods should remain stable while the underlying tables could grow. The constraints I care most about are the safety ones: emails use RFC 2606 reserved domains, IPs come from TEST-NET documentation ranges, and phones stay in the
fictional 555-01xx band. A mock-data library should never accidentally emit a real person's plausible-looking contact information.
The VIN generator was the most fun to get right. A real VIN has a check digit at position 9 computed from a weighted transliteration of the other 16 characters, mod 11. I implemented it, then wrote the test to independently recompute the check digit a different way and assert they agree. When two different implementations of a fiddly algorithm agree across 200 random VINs, you actually believe it.
The sleight of hand I'm quietly proud of
There was a real risk here. Back in M6, the type generators read from context.Random, which was an internal handle on the PRNG. In M7, Random has to become the public RandomData façade. Change that, and you risk shifting the byte stream and breaking the M6 golden, exactly the kind of seeded-output break the project treats as a serious event.
But RandomData.Int is a one-line forward to DeterministicRandom.Int. Same underlying instance, same draws, same bytes. So I swapped the type from internal to public, held my breath, ran the M6 golden… and it passed, untouched. The façade is
genuinely transparent. Threading a new public API over existing behavior without disturbing a single committed byte is the kind of small, invisible win that makes the careful-determinism discipline feel worth it.
Finally solving the tooling mystery
And then there's the dotnet format saga. Since M5 it had refused to auto-generate my public-API entries, warning about a "duplicate source file." I'd been hand-writing entries to get around it. This milestone added 75 new public members, enough to make the workaround obviously unreasonable, so I finally sat down and read the warning properly.
The culprit was sitting in my own csproj since M0: I'd explicitly added <AdditionalFiles Include="PublicAPI.*.txt" />, but the analyzer package already includes those files itself. Double-registered. Delete my redundant line, and dotnet format instantly worked, spitting out all 75 entries perfectly formatted.
A self-inflicted problem I'd been routing around for two milestones, fixed in thirty seconds once I investigated the message. The lesson was not about dotnet format; it was about how quickly a workaround can become invisible.
Where it leaves things
Lie.Define<Car>().Build().Generate() now returns a Mazda Sorento, a believable price, and an owner with a real-looking name and an @example.net email. The placeholders are gone. Every semantic name resolves to a real generator, every dataset draws from the one deterministic stream, and same-seed runs are identical.
The library finally does, end-to-end, the thing it set out to do.
What's next
M8, the finale. Explain(), so the inference isn't a black box; the cached zero-config Lie<T> automatic path; reflection-free construction, so generating a million objects allocates nothing but the objects themselves; a benchmark suite; and the final goldens.
Then the release-hardening checklist is done, and v1.0 is done.
Top comments (0)