Develop discriminating xUnit tests with Copilot
A test can pass while proving very little. Verify behavior of the production repository instead of testing a helper defined only inside the test.
Lab briefing

Original concept illustration (SVG)
| At a glance | Your route |
|---|---|
| Level and time | 300; 65 minutes (facilitation estimate) |
| Starting action | Add one found-ID component test and assert populated entities. |
| Learner materials | Download 04-xunit.zip |
| Workspace | Open the extracted kit root; run the baseline from . relative to that root |
| Expected initial check | The supplied tests pass. New feature requirements still need their own tests. |
| Setup help | Download, extract, local Git and optional GitHub |
[!NOTE] Mocking the method under test cannot prove its implementation works.
Concepts · First task · Evidence checklist · Reset
Learning objectives
- Distinguish unit, component and UI tests.
- Use existing xUnit and NSubstitute conventions.
- Isolate filesystem data and prove failure detection.
Before you start
Prepare 04-xunit with the common setup.
Use the bundled
xUnit fixture.
Do not replace xUnit with another framework or install packages globally.
Concepts and use cases
| Test type | Subject | Appropriate seam |
|---|---|---|
| Service unit test | Loan/membership decision | Substitute repository contract |
| Repository component test | Load/populate actual JSON records | Temporary files plus real JsonLoanRepository |
| Console check | Input and rendering | Manual scripted scenario |
The existing test project references ApplicationCore. Testing Infrastructure requires
an explicit project reference; a mocked GetLoan cannot test its own implementation.
Exercise scenario
Add repository tests for GetLoan: found, absent, and populated relationships.
Do not add undocumented type-conversion rules to an integer-ID API.
Task 1 - Inspect and discover the baseline
dotnet test tests/UnitTests/UnitTests.csproj -m:1 -p:UseSharedCompilation=false --list-tests
dotnet test tests/UnitTests/UnitTests.csproj -m:1 -p:UseSharedCompilation=false
Read JsonLoanRepository, JsonData, the existing service tests and LoanFactory.
Explain which tests use substitutes and which need actual storage behavior.
Task 2 - Design cases before generating tests
| Case | Assert |
|---|---|
| Existing ID | Returned loan fields match the fixture |
| Missing ID | Result is null; no synthetic success record |
| Found relationships | Correct patron, physical copy, book and author |
| Empty loans file | No false match |
| Read-only query | Source files remain unchanged |
| Missing/corrupt file | Characterize current loader behavior; do not invent a fallback |
Use a temporary directory on the work drive with the five JSON files and configuration
paths pointing there. Avoid writing to the distributed src/Library.Console/Json.
Task 3 - Plan and generate one test
Ask:
Trace GetLoan and JsonData.EnsureDataLoaded. What must a repository component test
construct? Cite the constructor and configuration keys. Do not mock the method
under test or create a replacement validation function in the test.
Plan the fixture lifecycle, expected data and cleanup. Then in Agent:
- Add the Infrastructure project reference to the test project.
- Create one found-ID component test using the real repository.
- Assert fields/relationships, not only “not null.”
- Use a
Task-returning test and await the method. - Add the remaining cases without duplicating setup excessively.
Task 4 - Validate test quality
-
Run the new filtered tests, then the fixture suite:
dotnet test tests/UnitTests/UnitTests.csproj -m:1 -p:UseSharedCompilation=false --filter "FullyQualifiedName~JsonLoanRepository" dotnet test tests/UnitTests/UnitTests.csproj -m:1 -p:UseSharedCompilation=false -
Confirm discovered names match the intended tests. Adjust the filter to the actual class name rather than accepting zero matches.
-
Deliberately change the expected ID. Verify failure, then restore it.
-
Confirm temporary data is cleaned up without deleting another test’s directory.
-
Optional: run the existing coverage collector. Report the actual metric and its scope; a percentage does not prove every requirement.
Verify your work
- Tests exercise the production repository.
- The async method is awaited.
- Fields and populated references are asserted.
- Files are isolated and read-only queries leave them unchanged.
- A wrong assertion produces nonzero exit.
Troubleshooting
An unresolved Infrastructure namespace usually means a missing project reference. A file not found often means configuration points at the source fixture or wrong working directory. Do not fix it by copying machine-specific absolute paths into code.
Independent practice
Test UpdateLoan for a known ID and document what happens for an absent ID.
Separate characterization from a proposed change in missing-ID semantics.
Reset
Restore only test/project-reference changes in the copied project. Remove its own temporary directory after verifying the path. Keep original JSON fixtures unchanged.