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="..." />
Angle brackets restored: the supplied text represented < 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 test in 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.
Do not mock everything. A repository test may need controlled real files if parsing or persistence is the behaviour being tested.

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.
Never optimise for green at any cost. AI can "fix" a failure by weakening an assertion or changing production behaviour incorrectly. The test must continue to represent the intended requirement.

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 modeReview question
Mirrors implementationWould the test fail if the business rule were implemented incorrectly in the same way?
Weak assertionDoes it assert the actual outcome or only "not null"?
Over-mockingDid mocks remove the behaviour we intended to test?
Happy-path biasWhere are invalid, boundary and failure cases?
Implementation couplingWill harmless refactoring break the test?
False isolationDoes 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.

Useful links