// 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"));
}
# Good: Single behaviour, no I/O, descriptive name
RSpec.describe PricingEngine do
it "applies tiered rates when calculating discounts" do
customer = Customer.new(tier: "gold", years_active: 3)
order = Order.new(total: 1000, items: 5)
discount = described_class.new.calculate(customer, order)
expect(discount).to eq(150) # 10% base + 5% loyalty
end
end
# Bad: Multiple assertions, external dependency, flaky
RSpec.describe "user registration" do
it "registers a user" do
response = HTTP.post("/api/users", json: { name: "..." })
expect(response.status).to eq(201)
expect(User.exists?(email: "...")).to be(true)
expect(sent_welcome_email?).to be(true)
end
end
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;
}
}
# Instead of hand-picked examples, generate thousands
require "prop_check"
G = PropCheck::Generators
PropCheck.forall(G.array(G.choose(0..100))) do |xs|
raise "sort is not idempotent" unless xs.sort == xs.sort.sort
end
PropCheck.forall(G.printable_ascii_string) do |s|
raise "roundtrip failed" unless unescape(escape(s)) == s
end
// 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"
});
}
}
# Consumer (frontend) defines expected contract — pact gem
require "pact/consumer/rspec"
Pact.service_consumer "OrderService" do
has_pact_with "PaymentService" do
mock_service :payment_service do
port 1234
end
end
end
payment_service
.given("a valid payment request")
.upon_receiving("a charge request")
.with(method: :post, path: "/payment/charge",
body: { amount_cents: 1999, currency: "USD", source: "tok_visa" })
.will_respond_with(status: 200,
body: { 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();
}
# Testcontainers: Real DB in container, auto-cleanup
require "testcontainers/postgres"
RSpec.describe UserRepository do
around do |example|
container = Testcontainers::PostgresContainer.new("postgres:16").start
@database_url = container.database_url
example.run
ensure
container&.stop
end
it "saves and loads users" do
repo = UserRepository.new(@database_url)
user = User.new(name: "Alice", email: "alice@example.com")
repo.save(user)
loaded = repo.find_by_email("alice@example.com")
expect(loaded.name).to eq("Alice")
expect(loaded.id).not_to be_nil
end
end