C# Architecture Mastery — Testing Strategies in Clean Architecture (.NET) (Part 7)

Published: (December 23, 2025 at 03:57 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

What You Test

Traditional systems test from the outside in.
Clean Architecture tests from the inside out.

Priority order

  1. Domain logic
  2. Application use cases
  3. Infrastructure integrations
  4. Web / API layer

This ordering is intentional.

Classic testing pyramid

  • Unit tests (many)
  • Integration tests (some)
  • End‑to‑end tests (few)

Clean Architecture sharpens it

LayerCoverage
Domain Tests███████████
Application Tests████████
Integration Tests████
API / E2E Tests██

Business rules deserve the most confidence.

Business rules

  • Invariants
  • Calculations
  • State transitions

Domain unit test

// ✅ Domain unit test
[Fact]
public void Order_Cannot_Be_Created_With_Negative_Total()
{
    var act = () => new Order(-10);
    act.Should().Throw();
}

If domain tests require mocks, something is wrong.

Application test with mocks

// ✅ Application test with mocks
[Fact]
public async Task CreateOrder_Saves_Order_And_Sends_Notification()
{
    var repo = Substitute.For<IOrderRepository>();
    var notifier = Substitute.For<INotifier>();

    var useCase = new CreateOrderUseCase(repo, notifier);

    await useCase.Execute(new CreateOrderCommand(100));

    await repo.Received(1).SaveAsync(Arg.Any<Order>());
    await notifier.Received(1).NotifyAsync(Arg.Any<Notification>());
}

Mocks belong only at the boundaries.

Infrastructure tests

Infrastructure tests answer: “Does this thing actually work?”

  • EF Core mappings
  • SQL queries
  • External APIs
  • File systems
  • Message brokers

EF Core integration test

// ✅ EF Core integration test
using var db = CreateRealDbContext();
var repo = new OrderRepository(db);

await repo.SaveAsync(order);
var saved = await db.Orders.FindAsync(order.Id);

saved.Should().NotBeNull();

These tests are slower, fewer, but essential.

API tests

API tests should validate:

  • Routing
  • HTTP contracts
  • Serialization
  • Authentication / authorization

They should not re‑test business rules.

// ✅ API test
var response = await client.PostAsJsonAsync("/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.OK);

If API tests are complex, controllers are probably fat.

Do not test

  • Framework internals
  • EF Core itself
  • ASP.NET Core routing logic
  • Microsoft libraries

Test your code, not theirs.

Warning signs

  • Mocking DbContext everywhere
  • Integration tests replacing unit tests
  • Tests coupled to HTTP models
  • Tests breaking on refactors
  • Massive test setup code

These usually indicate boundary violations.

Testing is not about coverage

It is about:

  • Confidence
  • Change safety
  • Design feedback

Good tests make refactoring boring.

Before shipping, ask

  • Are business rules tested without infrastructure?
  • Do use cases have focused unit tests?
  • Are integrations tested against real systems?
  • Are API tests thin?
  • Do tests guide design decisions?

If yes, your architecture is working.

If testing feels painful

  • Revisit boundaries
  • Remove leaks
  • Simplify responsibilities

Great tests are a side‑effect of great architecture.

Back to Blog

Related posts

Read more »