// Good: Single behavior, no I/O, descriptive name
TEST(PricingEngineTest, AppliesTieredRatesForGoldCustomer) {
Customer customer("gold", 3);
Order order(1000, 5);
Money discount = PricingEngine().calculate(customer, order);
ASSERT_EQ(Money(150), discount);
}
// Bad: Multiple assertions, external dependency, flaky
TEST(UserRegistrationTest, BadExample) {
auto response = Post("/api/users", json{...});
ASSERT_EQ(201, response.status_code());
ASSERT_TRUE(User::Exists("alice@example.com"));
ASSERT_TRUE(EmailSent("welcome"));
}
// Good: Single behavior, no I/O, descriptive name
@Test
void testCalculateDiscountAppliesTieredRates() {
Customer customer = new Customer("gold", 3);
Order order = new Order(1000, 5);
Money discount = pricingEngine.calculate(customer, order);
assertEquals(150, discount.getCents());
}
// Bad: Multiple assertions, external dependency, flaky
@Test
void testUserRegistration() {
var response = post("/api/users", json);
assertEquals(201, response.getStatusCode());
assertTrue(User.exists("alice@example.com"));
assertTrue(Email.wasSent("welcome"));
}
// Good: Single behavior, no I/O, descriptive name
[Fact]
public void CalculateDiscount_AppliesTieredRates_ForGoldCustomer() {
var customer = new Customer("gold", 3);
var order = new Order(1000, 5);
var discount = _pricingEngine.Calculate(customer, order);
Assert.Equal(new Money(150), discount);
}
// Bad: Multiple assertions, external dependency, flaky
[Fact]
public void UserRegistration_BadExample() {
var response = _client.Post("/api/users", json);
Assert.Equal(201, response.StatusCode);
Assert.True(User.Exists("alice@example.com"));
Assert.True(Email.WasSent("welcome"));
}
import net.jqwik.api.*;
import java.util.*;
class PropertyTests {
@Property
boolean sortIsIdempotent(@ForAll List<Integer> xs) {
var sortedOnce = new ArrayList<>(xs);
Collections.sort(sortedOnce);
var sortedTwice = new ArrayList<>(sortedOnce);
Collections.sort(sortedTwice);
return sortedOnce.equals(sortedTwice);
}
@Property
boolean escapeUnescapeRoundtrip(@ForAll String s) {
return unescape(escape(s)).equals(s);
}
}
// FsCheck for C#
using FsCheck;
using FsCheck.Xunit;
public class PropertyTests {
[Property]
public bool SortIsIdempotent(List<int> xs) {
var sortedOnce = xs.OrderBy(x => x).ToList();
var sortedTwice = sortedOnce.OrderBy(x => x).ToList();
return sortedOnce.SequenceEqual(sortedTwice);
}
[Property]
public bool EscapeUnescapeRoundtrip(string s) {
return Unescape(Escape(s)) == s;
}
}
// Pact C++ is limited
// Use pact-verifier CLI for verification
# pact-verifier --provider-base-url=http://localhost:8080 \
# --pact-url=./pacts/order_service-payment_service.json
// PactNet
using PactNet;
using Xunit;
public class PaymentContractTests {
[Fact]
public void PaymentContract() {
var config = new PactConfig { PactDir = "./pacts" };
var pact = new PactVerifier(new PactConfig { PactDir = "./pacts" });
var builder = new V2PactBuilder("OrderService", "PaymentService");
builder
.Given("a valid payment request")
.UponReceiving("a charge request")
.With(new ProviderServiceRequest {
Method = HttpVerb.Post,
Path = "/payment/charge",
Body = new { amount_cents = 1999, currency = "USD", source = "tok_visa" }
})
.WillRespondWith(200, new {
transaction_id = "txn_123",
status = "succeeded"
});
}
}
Approach
Pros
Cons
Consumer-driven (Pact)
Prevents breaking changes
Requires broker
Provider-driven (OpenAPI)
Single source of truth
May miss consumer needs
Bi-directional
Both sides validated
More complex setup
5. Integration Testing — Real Dependencies
# Testcontainers: Real DB in container, auto-cleanup
@pytest.fixture
def postgres():
with PostgresContainer("postgres:16") as pg:
yield pg.get_connection_url()
def test_user_repository_saves_and_loads(postgres):
repo = UserRepository(postgres)
user = User(name="Alice", email="alice@example.com")
repo.save(user)
loaded = repo.find_by_email("alice@example.com")
assert loaded.name == "Alice"
assert loaded.id is not None
# Testcontainers C++ (using testcontainers-cpp or similar)
#include <testcontainers/postgres.hpp>
using namespace testcontainers;
auto postgres = PostgresContainer("postgres:16").start();
auto conn = postgres.get_connection_url();
// Or use testcontainers-cpp library
auto container = PostgresContainer()
.withImageName("postgres:16")
.withStartupTimeout(std::chrono::seconds(60))
.start();
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Test
void testUserRepositorySavesAndLoads() {
var repo = new UserRepository(postgres.getJdbcUrl());
var user = new User("Alice", "alice@example.com");
repo.save(user);
var loaded = repo.findByEmail("alice@example.com");
assertEquals("Alice", loaded.getName());
assertNotNull(loaded.getId());
}
}
using Testcontainers.PostgreSql;
public class UserRepositoryTests : IAsyncLifetime {
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:16")
.Build();
public async Task InitializeAsync() => await _postgres.StartAsync();
[Fact]
public async Task TestUserRepositorySavesAndLoads() {
var repo = new UserRepository(_postgres.GetConnectionString());
var user = new User("Alice", "alice@example.com");
await repo.SaveAsync(user);
var loaded = await repo.FindByEmailAsync("alice@example.com");
Assert.Equal("Alice", loaded.Name);
Assert.NotNull(loaded.Id);
}
public async Task DisposeAsync() => await _postgres.DisposeAsync();
}