Lesson 5 of 8 · AI for Developers: Code Faster with AI Assistants

Writing Unit Tests With AI, and Reviewing Them Properly

Use AI to list test cases and write xUnit tests quickly, then review them so they actually catch bugs instead of just raising coverage.

13 min · Updated 21 Oct 2026

Tests are one of the best uses of AI in coding: they are repetitive, follow clear patterns, and many developers skip them when short of time. But AI-written tests have a classic weakness. They often test what the code does rather than what it should do, so they pass even when the code is wrong.

Step 1: Ask for test cases before test code

Paste the method and ask for cases only:

Here is a C# method. List the test cases it needs: happy paths, edge
cases, invalid inputs and failure modes. One line per case with the
input and the expected result. Do not write code yet.

public static decimal ApplyDiscount(decimal total, string? code, DateTime nowUtc)
{
    ...
}

Read the list against the requirements, not the code. If the business rule says "discounts never make the total negative", that case must be there, even if the code does not handle it yet. That is how tests find bugs.

Add any missing cases yourself, then move on.

Step 2: Ask for tests in your style

Write xUnit tests for these cases. Use [Theory] with [InlineData] where
cases differ only by data. Name tests Method_Scenario_Expected. Use plain
Assert (no FluentAssertions). Pass a fixed nowUtc instead of using the
real clock.

A typical result:

public class DiscountTests
{
    private static readonly DateTime Now = new(2026, 10, 21, 10, 0, 0, DateTimeKind.Utc);

    [Theory]
    [InlineData(1000, "SAVE10", 900)]
    [InlineData(1000, null, 1000)]
    [InlineData(1000, "UNKNOWN", 1000)]
    public void ApplyDiscount_KnownAndUnknownCodes_ReturnsExpectedTotal(
        decimal total, string? code, decimal expected)
    {
        Assert.Equal(expected, Pricing.ApplyDiscount(total, code, Now));
    }

    [Fact]
    public void ApplyDiscount_FlatDiscountLargerThanTotal_NeverGoesNegative()
    {
        Assert.Equal(0m, Pricing.ApplyDiscount(50, "FLAT100", Now));
    }
}

Passing the time in (nowUtc) instead of reading DateTime.UtcNow inside the method makes date logic testable. Ask the AI to suggest this kind of change when code is hard to test.

Step 3: Review the tests

Check every AI-written test for these problems:

Problem What it looks like
Tests the implementation Expected values copied from the current output, not from the requirement
Asserts too little Only Assert.NotNull(result)
Over-mocked Mocks so many things that nothing real is tested
Hidden randomness Uses DateTime.Now, Guid.NewGuid() or Random in assertions
Duplicate tests Five tests checking the same path with different names
Wrong reason to pass Passes because an exception is swallowed

Step 4: Prove the tests can fail

A test that has never failed has not proved anything. Quick checks:

  • Break the code on purpose, for example change >= to >, and run the tests. At least one should fail. Undo the change.
  • For a bug fix, write the test first and see it fail before the fix (lesson 4).

You can ask the AI to help:

Suggest 5 small deliberate bugs I could introduce in this method to check
whether these tests catch them.

This is mutation testing done by hand. Tools such as Stryker.NET automate it for larger codebases.

Integration tests

For API endpoints and EF Core queries, unit tests with mocks often miss real problems. Ask the AI to help set up integration tests with WebApplicationFactory and a real test database, or a container-based database, for the important flows.

Practice task

Choose one method with real business logic. Get a test case list from the AI, add the cases it missed from the requirements, generate xUnit tests, review them with the table above, and break the code on purpose to confirm the tests fail.

Key takeaways

  • Ask for test cases first and check them against the requirements, not the code.
  • Specify your test style: framework, naming, assertion library, no real clock.
  • Review for implementation-copied expectations, weak asserts and over-mocking.
  • Break the code on purpose to prove the tests can fail.

Finished this lesson?Mark it complete to track your progress on this device.