Course contentsShow
← C#
Lesson 1614 of 1,90815. Software Quality and TestingFree lesson

Testing State vs. Testing Behavior

Distinguish between verifying object internal state and verifying interactions between components.

In this lesson, you will learn how to choose between asserting the final outcome of an object's properties versus verifying the specific sequence of calls between collaborating components.

When we write tests in C#, we often default to inspecting the Result or the Properties of an object after an action. This is State-Based Testing. It is the most robust approach because it treats your object as a black box: you provide input, trigger a method, and verify that the final state matches your expectations.

// State-Based Testing Example
var cart = new ShoppingCart();
cart.AddItem(new Item("Book", 10.00m));

Assert.Equal(10.00m, cart.TotalAmount); // Verifying final state

However, in distributed systems or complex services, an object’s primary job is often to coordinate with other services (like a message bus or a database repository). If the result of an operation is a side effect—such as sending an event or updating a remote dependency—checking internal properties isn't enough. This is where Behavior-Based Testing (or interaction testing) comes in.

Behavior-based testing uses "Mocks" or "Spies" to verify that an object interacted with its dependencies exactly as expected.

// Behavior-Based Testing Example
var mockBus = new Mock<IMessageBus>();
var service = new OrderService(mockBus.Object);

service.PlaceOrder(order);

// Verifying the interaction, not the internal state
mockBus.Verify(x => x.Publish(It.IsAny<OrderPlacedEvent>()), Times.Once);

Which should you choose?

  • Use State Testing for domain logic, calculators, and entities. It makes your tests less brittle; you can refactor the internal implementation (like changing a list to a dictionary) without breaking the test, as long as the public result remains the same.
  • Use Behavior Testing when the code's primary responsibility is orchestration—sending emails, triggering background jobs, or logging. Only mock what you don't own (external APIs, message brokers, or hardware interfaces).

Over-using behavior testing leads to "over-specification," where tests fail even when the logic is correct simply because you changed a private implementation detail, such as the order of method calls.

Key Takeaway: Prefer state-based testing to verify outcomes, and reserve behavior-based testing for verifying the protocols and interactions between your system components.