Test Styles
Aver supports two styles for writing tests. Both are first-class — neither is wrong. Choose based on who reads the test and what it’s expressing.
The two styles
Section titled “The two styles”given/when/then — aliases that add narrative structure:
test('quantity discount kicks in at 10 items', async ({ given, then }) => { await given.addLineItem({ product: 'Widget', quantity: 10, unitPrice: 10.00 }) await then.discountApplied({ percent: 10 }) await then.totalEquals({ expected: 97.20 })})@s.testdef test_quantity_discount_kicks_in_at_10_items(ctx): ctx.given.add_line_item(product="Widget", quantity=10, unit_price=10.00) ctx.then.discount_applied(percent=10) ctx.then.total_equals(expected=97.20)s.test "quantity discount kicks in at 10 items" do |ctx| ctx.given.add_line_item(product: "Widget", quantity: 10, unit_price: 10.00) ctx.then.discount_applied(percent: 10) ctx.then.total_equals(expected: 97.20)ends.Run(t, "quantity discount kicks in at 10 items", func(ctx aver.TestCtx) { aver.Given(ctx, Pricing.AddLineItem, AddLineItemParams{Product: "Widget", Quantity: 10, UnitPrice: 10.00}) aver.Then(ctx, Pricing.DiscountApplied, DiscountParams{Percent: 10}) aver.Then(ctx, Pricing.TotalEquals, TotalParams{Expected: 97.20})})s.run("quantity discount kicks in at 10 items", |m, ctx| { ctx.given(&m.add_line_item, AddLineItemParams { product: "Widget", quantity: 10, unit_price: 10.00 }); ctx.then(&m.discount_applied, DiscountParams { percent: 10 }); ctx.then(&m.total_equals, TotalParams { expected: 97.20 });});@Test fun `quantity discount kicks in at 10 items`() = s.run { ctx -> ctx.Given(pricing.addLineItem, AddLineItemParams("Widget", 10, 10.00)) ctx.Then(pricing.discountApplied, DiscountParams(10)) ctx.Then(pricing.totalEquals, TotalParams(97.20))}act/assert — direct names without narrative framing:
test('move task through workflow', async ({ act, assert }) => { await act.createTask({ title: 'Fix login bug' }) await act.moveTask({ title: 'Fix login bug', status: 'in-progress' }) await assert.taskInStatus({ title: 'Fix login bug', status: 'in-progress' })})@s.testdef test_move_task_through_workflow(ctx): ctx.act.create_task(title="Fix login bug") ctx.act.move_task(title="Fix login bug", status="in-progress") ctx.assert_.task_in_status(title="Fix login bug", status="in-progress")s.test "move task through workflow" do |ctx| ctx.act.create_task(title: "Fix login bug") ctx.act.move_task(title: "Fix login bug", status: "in-progress") ctx.assert.task_in_status(title: "Fix login bug", status: "in-progress")ends.Run(t, "move task through workflow", func(ctx aver.TestCtx) { aver.Act(ctx, TaskBoard.CreateTask, CreateTaskParams{Title: "Fix login bug"}) aver.Act(ctx, TaskBoard.MoveTask, MoveTaskParams{Title: "Fix login bug", Status: "in-progress"}) aver.Assert(ctx, TaskBoard.TaskInStatus, TaskInStatusParams{Title: "Fix login bug", Status: "in-progress"})})s.run("move task through workflow", |m, ctx| { ctx.act(&m.create_task, CreateTaskParams { title: "Fix login bug".into() }); ctx.act(&m.move_task, MoveTaskParams { title: "Fix login bug".into(), status: "in-progress".into() }); ctx.assert(&m.task_in_status, TaskInStatusParams { title: "Fix login bug".into(), status: "in-progress".into() });});@Test fun `move task through workflow`() = s.run { ctx -> ctx.Act(taskBoard.createTask, CreateTaskParams("Fix login bug")) ctx.Act(taskBoard.moveTask, MoveTaskParams("Fix login bug", "in-progress")) ctx.Assert(taskBoard.taskInStatus, TaskInStatusParams("Fix login bug", "in-progress"))}Under the hood, given and when are both aliases for act, and then is an alias
for assert. You can mix them freely within a test.
When to prefer given/when/then
Section titled “When to prefer given/when/then”- Multi-step scenarios where setup, trigger, and verification are distinct phases
- Stakeholder-readable specs produced from Example Mapping or BDD workshops
- Living documentation — the test name and step labels together tell a story
- Onboarding — clearer for developers new to the domain
test('new member gets welcome discount on first order', async ({ given, when, then }) => { await given.memberSignedUp({ email: 'alice@example.com' }) await when.placesOrder({ items: [{ sku: 'W-100', qty: 1 }] }) await then.discountApplied({ percent: 15 })})@s.testdef test_new_member_gets_welcome_discount_on_first_order(ctx): ctx.given.member_signed_up(email="alice@example.com") ctx.when.places_order(items=[{"sku": "W-100", "qty": 1}]) ctx.then.discount_applied(percent=15)s.test "new member gets welcome discount on first order" do |ctx| ctx.given.member_signed_up(email: "alice@example.com") ctx.when.places_order(items: [{ sku: "W-100", qty: 1 }]) ctx.then.discount_applied(percent: 15)ends.Run(t, "new member gets welcome discount on first order", func(ctx aver.TestCtx) { aver.Given(ctx, Membership.MemberSignedUp, SignUpParams{Email: "alice@example.com"}) aver.When(ctx, Orders.PlacesOrder, OrderParams{Items: []Item{{SKU: "W-100", Qty: 1}}}) aver.Then(ctx, Pricing.DiscountApplied, DiscountParams{Percent: 15})})s.run("new member gets welcome discount on first order", |m, ctx| { ctx.given(&m.member_signed_up, SignUpParams { email: "alice@example.com".into() }); ctx.when(&m.places_order, OrderParams { items: vec![Item { sku: "W-100", qty: 1 }] }); ctx.then(&m.discount_applied, DiscountParams { percent: 15 });});@Test fun `new member gets welcome discount on first order`() = s.run { ctx -> ctx.Given(membership.memberSignedUp, SignUpParams("alice@example.com")) ctx.When(orders.placesOrder, OrderParams(listOf(Item("W-100", 1)))) ctx.Then(pricing.discountApplied, DiscountParams(15))}When to prefer act/assert
Section titled “When to prefer act/assert”- Simple operations — setup and verification are one step each
- Developer-facing tests where narrative framing adds no signal
- Query-heavy tests that don’t map to a given/when/then arc
test('assign task to team member', async ({ act, assert }) => { await act.createTask({ title: 'Fix login bug' }) await act.assignTask({ title: 'Fix login bug', assignee: 'Alice' }) await assert.taskAssignedTo({ title: 'Fix login bug', assignee: 'Alice' })})@s.testdef test_assign_task_to_team_member(ctx): ctx.act.create_task(title="Fix login bug") ctx.act.assign_task(title="Fix login bug", assignee="Alice") ctx.assert_.task_assigned_to(title="Fix login bug", assignee="Alice")s.test "assign task to team member" do |ctx| ctx.act.create_task(title: "Fix login bug") ctx.act.assign_task(title: "Fix login bug", assignee: "Alice") ctx.assert.task_assigned_to(title: "Fix login bug", assignee: "Alice")ends.Run(t, "assign task to team member", func(ctx aver.TestCtx) { aver.Act(ctx, TaskBoard.CreateTask, CreateTaskParams{Title: "Fix login bug"}) aver.Act(ctx, TaskBoard.AssignTask, AssignTaskParams{Title: "Fix login bug", Assignee: "Alice"}) aver.Assert(ctx, TaskBoard.TaskAssignedTo, AssignedParams{Title: "Fix login bug", Assignee: "Alice"})})s.run("assign task to team member", |m, ctx| { ctx.act(&m.create_task, CreateTaskParams { title: "Fix login bug".into() }); ctx.act(&m.assign_task, AssignTaskParams { title: "Fix login bug".into(), assignee: "Alice".into() }); ctx.assert(&m.task_assigned_to, AssignedParams { title: "Fix login bug".into(), assignee: "Alice".into() });});@Test fun `assign task to team member`() = s.run { ctx -> ctx.Act(taskBoard.createTask, CreateTaskParams("Fix login bug")) ctx.Act(taskBoard.assignTask, AssignTaskParams("Fix login bug", "Alice")) ctx.Assert(taskBoard.taskAssignedTo, AssignedParams("Fix login bug", "Alice"))}Mixing styles
Section titled “Mixing styles”Because given/when are just act and then is just assert, mixing is valid when
it improves clarity:
test('checkout flow', async ({ given, when, assert }) => { await given.cartHasItems({ count: 3 }) await when.checkout({ paymentMethod: 'card' }) await assert.orderConfirmed() await assert.cartEmpty()})@s.testdef test_checkout_flow(ctx): ctx.given.cart_has_items(count=3) ctx.when.checkout(payment_method="card") ctx.assert_.order_confirmed() ctx.assert_.cart_empty()s.test "checkout flow" do |ctx| ctx.given.cart_has_items(count: 3) ctx.when.checkout(payment_method: "card") ctx.assert.order_confirmed ctx.assert.cart_emptyends.Run(t, "checkout flow", func(ctx aver.TestCtx) { aver.Given(ctx, Cart.CartHasItems, CartItemsParams{Count: 3}) aver.When(ctx, Cart.Checkout, CheckoutParams{PaymentMethod: "card"}) aver.Assert(ctx, Orders.OrderConfirmed, struct{}{}) aver.Assert(ctx, Cart.CartEmpty, struct{}{})})s.run("checkout flow", |m, ctx| { ctx.given(&m.cart_has_items, CartItemsParams { count: 3 }); ctx.when(&m.checkout, CheckoutParams { payment_method: "card".into() }); ctx.assert(&m.order_confirmed, ()); ctx.assert(&m.cart_empty, ());});@Test fun `checkout flow`() = s.run { ctx -> ctx.Given(cart.cartHasItems, CartItemsParams(3)) ctx.When(cart.checkout, CheckoutParams("card")) ctx.Assert(orders.orderConfirmed) ctx.Assert(cart.cartEmpty)}Summary
Section titled “Summary”| Style | Best for |
|---|---|
given/when/then | Multi-step scenarios, stakeholder specs, BDD workflows |
act/assert | Simple operations, developer-facing tests |