GitHub Copilot can accelerate unit-test development by helping identify scenarios, scaffold test projects, generate tests, extend edge-case coverage and diagnose failures. The strongest workflow combines Visual Studio Code's normal testing tools with Copilot's Ask, Plan and Agent modes: humans define the behaviour and testing strategy, Copilot accelerates repetitive implementation and the test runner provides objective feedback.
Set up the test environment first
Copilot generates test code, but the project still needs a working test framework and runner.
- .NET SDK.
- C# Dev Kit.
- A test framework such as xUnit, NUnit or MSTest.
- A project reference from the test project to the code under test.
Example project reference
dotnet add tests/UnitTests/UnitTests.csproj reference src/MyApp/MyApp.csproj
Typical xUnit package references
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="..." />
<PackageReference Include="xunit" Version="..." />
<PackageReference Include="xunit.runner.visualstudio" Version="..." />
<PackageReference Include="coverlet.collector" Version="..." />
< and > with placeholders. Here they are restored semantically and HTML-escaped where necessary so the rendered article shows correct C# and XML syntax.Use Test Explorer as the source of truth
Visual Studio Code's testing integration provides discovery, run/debug commands and failure output independently from Copilot.
- Run one test from the editor.
- Run a class or suite from Test Explorer.
- Debug failing tests.
- Navigate from stack traces to source.
- Use terminal commands such as
dotnet testin CI-like conditions.
Ask mode before generating tests
Ask mode is useful for deciding what should be tested before any file is modified.
What unit-test scenarios should cover CalculateDiscount?
Include:
- normal values
- zero
- boundaries
- invalid input
- dependency failures
- state changes
- cases that should not be unit tested
Explain the reason for each scenario.
This helps prevent a common AI failure mode: generating tests that merely mirror the implementation rather than challenge the behaviour.
Use /setupTests when testing infrastructure is missing
When supported in the current Copilot environment, /setupTests can help configure a suitable test project and framework. In Agent mode it may also scaffold files or run package-management commands.
Always review the framework choice, package versions, project references, folder structure and CI compatibility.
Generate tests with /tests
Whole-file example
/tests Generate unit tests for the methods in this file.
Use xUnit.
Include:
- success cases
- failure cases
- boundaries
- invalid inputs
Follow existing naming and Arrange-Act-Assert patterns.
Selection example
/tests #selection
Generate tests only for the selected method.
Do not change production code.
Explain any dependency that must be mocked.
Natural-language test generation
Generate xUnit tests for CalculateDiscount.
Rules:
- use Theory for boundary combinations where useful
- use Fact for unique behavioural cases
- no shared mutable test state
- assert observable behaviour, not implementation details
- run the tests after creation
Plan larger testing work before implementing it
Plan mode is valuable when a testing task spans multiple classes, fixtures or dependencies.
Create a test plan for the Infrastructure data-access layer.
Use xUnit.
Identify:
- classes and methods to cover
- happy paths
- not-found paths
- file-system interactions
- what should be mocked
- what should use controlled real test data
- fixtures/factories required
- test file structure
- commands to verify the suite
Do not edit files yet.
Hand an approved plan to Agent mode
Implement the approved Infrastructure unit-test plan.
Requirements:
- use xUnit
- follow existing test naming
- reuse current test factories
- use NSubstitute only where isolation is required
- do not modify production behaviour to make a test pass
- run dotnet test
- summarise any remaining failing tests
Test factories and mocks
The supplied exercise demonstrates a useful pattern: factories create consistent domain objects while mocks isolate service dependencies.
Factories help by
- Reducing repetitive setup.
- Making scenarios easier to read.
- Providing consistent defaults.
- Creating boundary states deliberately.
Mocks help by
- Isolating business logic.
- Controlling dependency responses.
- Simulating failures.
- Verifying important interactions.
Example repository method
public async Task<Loan?> GetLoan(int id)
{
await _jsonData.EnsureDataLoaded();
foreach (Loan loan in _jsonData.Loans!)
{
if (loan.Id == id)
{
return _jsonData.GetPopulatedLoan(loan);
}
}
return null;
}
Useful tests
- Known ID returns the expected populated loan.
- Unknown ID returns
null. - Required data is loaded before lookup.
- Related entities are populated correctly when that is part of the repository contract.
Generate tests from existing project patterns
Copilot performs better when the repository already contains examples of the desired style.
#codebase Create tests for JsonLoanRepository.GetLoan.
Follow the style used by ReturnLoan tests.
Reuse LoanFactory where appropriate.
Use NSubstitute only for interfaces.
Include found and not-found cases.
Ghost text for extending coverage
Once a test file contains good examples, ghost text is an efficient way to add similar cases.
// Test that renewal is rejected when an overdue loan exists
Copilot can use the surrounding Arrange-Act-Assert pattern, imports and helper factories to suggest the rest.
- Boundary variants.
- Additional invalid inputs.
- Repeated state combinations.
- Parameterized tests that follow an established pattern.
Fix failing tests carefully
Copilot can assist with failing tests from Test Explorer or through the /fixTestFailure workflow.
1. Read the failure.
2. Decide whether production code or test expectation is wrong.
3. Ask Copilot to explain the failure.
4. Review the proposed fix.
5. Apply only the justified change.
6. Rerun the focused test.
7. Run the broader suite.
Agent-driven test repair
Run the xUnit tests.
For each failure:
- explain the root cause
- state whether you propose changing test or production code
- do not weaken assertions merely to make tests pass
- preserve documented business rules
Apply fixes only when the cause is supported by #codebase.
Rerun the affected tests.
Custom instructions for consistent tests
Repository instructions can encode team testing conventions so generated tests repeatedly follow the same rules.
---
applyTo: "tests/**"
---
Use xUnit.
Use Arrange-Act-Assert.
Prefer descriptive test names.
Use Theory for data-driven boundary tests.
Do not use Thread.Sleep.
Mock external dependencies, not domain entities.
Do not modify production code unless explicitly requested.
Every bug fix must include a regression test.
AI-generated tests can be misleading
| Failure mode | Review question |
|---|---|
| Mirrors implementation | Would the test fail if the business rule were implemented incorrectly in the same way? |
| Weak assertion | Does it assert the actual outcome or only "not null"? |
| Over-mocking | Did mocks remove the behaviour we intended to test? |
| Happy-path bias | Where are invalid, boundary and failure cases? |
| Implementation coupling | Will harmless refactoring break the test? |
| False isolation | Does a supposedly unit-level test touch real shared state? |
QA engineer review of generated unit tests
Behaviour
- Does each test map to a real rule?
- Are important invalid states represented?
- Are state transitions verified?
Isolation
- Is external state controlled?
- Are mocks used only where appropriate?
- Can tests run independently and in any order?
Maintainability
- Are names readable?
- Is setup reusable without hiding intent?
- Are assertions focused?
- Is duplicated test data handled sensibly?
Testing workflow
Understand requirement
↓
Ask: enumerate scenarios
↓
Plan: test structure
↓
Agent: scaffold / generate
↓
Review tests
↓
Run focused tests
↓
Add edge cases with ghost text
↓
Fix genuine failures
↓
Run full suite
↓
Human code review
Unit-testing checklist
- Test project builds independently.
- Framework and package versions are intentional.
- Tests derive expected behaviour from requirements.
- Happy, negative and boundary cases are covered.
- Mocks do not replace the behaviour under test.
- Factories improve readability rather than hide state.
- Tests are deterministic.
- No arbitrary sleeps are introduced.
- Generated assertions are meaningful.
- Failure-fixing does not weaken tests.
- Focused tests and full suite are run.
- Generated changes are reviewed before commit.
- Shared test conventions are captured in instructions where useful.