Character Copy
Level: Intermediate 30–60 minConcepts: Mocking
Solutions: C# | TypeScript | Python
In the interest of better understanding how to isolate dependencies through mocking frameworks, write a character copier class that reads characters from a source and copies them to a destination. It must copy and write one character at a time.
To do this create a Copier class that takes in a ISource and IDestination. ISource has one method char ReadChar() and IDestination has one method void WriteChar(char c). The Copier class has one method void Copy() that when called reads from the ISource one character at a time and write to IDestination as seen in Figure 1 below.
The copying and writing is done character at a time until a newline (‘\n’) is encountered. Then the processing stops without writing the newline. Only the Copier class may exist as a concrete. You are to use a mocking framework to implement ISource and IDestination.

Bonus
Once you have a single character working add a new method to both the ISource and IDestination that take will read and write multiple characters at a time. E.g. ReadChars(int count), WriteChars(char[] values). Be mindful not to read beyond the newline when copying between source and destination.
Solutions
You can find all language solutions here Character Copy Solutions
Or you can select a specific language below.
Reference Walkthrough
Reference implementations in C#, TypeScript, and Python live at tddbuddy-reference-katas/character-copy. Six scenarios — mapped 1:1 across all three languages — cover empty input, single character, multi-character, whitespace preservation, no-read-past-terminator, and exact-write-count. Each implementation defines two one-method collaborator interfaces (Source with readChar / read, and Destination with writeChar / write) alongside a three-line copy procedure that loops until '\n' is read and never writes the terminator.
- C# (.NET 8, xUnit, FluentAssertions 6.12.0) — walkthrough
- TypeScript (Node 20, Vitest 1.6, TS 5 strict) — walkthrough
- Python (3.11, pytest) — walkthrough
This kata ships in Agent Full-Bake mode at high gear. It is the one F1 kata whose SUT is not a pure function — it’s Kent Beck’s classic “mocking” kata, where the copier’s behaviour is the sequence of calls it makes to its collaborators, so you cannot drive it without test doubles. The reference deliberately uses hand-rolled fakes (StringSource, RecordingDestination) rather than a mocking framework: a two-method interface with a five-line fake is clearer than any mock.Setup().Returns(...) chain, and assertions on destination.written read as a sentence about the domain (“the destination wrote ‘abc’”) rather than about call sequences. F3 katas like bank-account/ apply the same collaborator-interface-plus-fake discipline at larger scale — this F1 is where the pattern is introduced in its smallest form. The bonus batch operations (readChars(n) / writeChars(chars)) are intentionally out of scope. See the repo’s Gears section for when high gear is the right call.
Related reading
- The Contract Test Is the Only Witness the Agent Cannot Author
A test the agent wrote against code the same agent wrote shares the agent's blind spot: a misunderstanding in the implementation becomes a matching misunderstanding in the test, the bar goes green, and the bug ships certified. Consumer-driven contract tests are the only category where a second team publishes the assertion the first team has to satisfy. That separation of authorship is exactly what tamper-resistant test design demanded, applied at the integration seam. - Cohesion Is the Coverage of the Agent Era
Agent-generated codebases pass every structural-modularity metric while fragmenting the domain concept the team was trying to encode. Coverage measured whether a line ran. Mutation score measured whether an assertion pinned behavior. Cohesion is the next axis, and it measures whether the codebase's concepts are traversable end-to-end. - Test Deletion Is a Privileged Operation
The cheapest way for an agent to make a failing test pass is to delete it. That is logical for the agent and catastrophic for the codebase. Tests are append-only by default. Deletion needs a human author, a separate commit, and a separate review.