149 lines
3.2 KiB
Markdown
149 lines
3.2 KiB
Markdown
---
|
|
name: test-writer
|
|
description: Writes tests following Elixir/Phoenix best practices. Ensures DRY tests with proper helper functions, no duplicated setup code, and correct parameter defaults. Use when writing or modifying tests.
|
|
---
|
|
|
|
# Test Writer Skill
|
|
|
|
## Overview
|
|
|
|
This skill provides guidelines for writing clean, maintainable Elixir/Phoenix tests following best practices. It focuses mainly on integration tests. For unit tests, we also want glanceable tests with unique names for values, test helpers, custom matchers and shared setups where appropriate.
|
|
|
|
## Core Principles
|
|
|
|
### 1. DRY Tests
|
|
|
|
Avoid duplication by creating focused helper functions:
|
|
|
|
**Bad:**
|
|
```elixir
|
|
test "GET /users returns index", %{conn: conn} do
|
|
conn = get(conn, "/users")
|
|
body = html_response(conn, 200)
|
|
assert body =~ "Users"
|
|
end
|
|
|
|
test "GET /users/:id returns show", %{conn: conn} do
|
|
conn = get(conn, "/users/1")
|
|
body = html_response(conn, 200)
|
|
assert body =~ "User"
|
|
end
|
|
```
|
|
|
|
**Good:**
|
|
```elixir
|
|
defp goto_users_page(conn, suffix \\ ""), do: get(conn, "/users" <> suffix)
|
|
|
|
test "GET /users returns index", %{conn: conn} do
|
|
conn = goto_users_page(conn)
|
|
assert html_response(conn, 200) =~ "Users"
|
|
end
|
|
|
|
test "GET /users/:id returns show", %{conn: conn} do
|
|
conn = goto_users_page(conn, "/1")
|
|
assert html_response(conn, 200) =~ "User"
|
|
end
|
|
```
|
|
|
|
|
|
### 2. Separate Helpers for Different Assertion Patterns
|
|
|
|
Don't use conditionals in helpers to handle different cases:
|
|
|
|
**Bad:**
|
|
```elixir
|
|
defp goto_users_page(conn, suffix \\ "", check_title \\ true) do
|
|
path = "/users" <> suffix
|
|
conn = get(conn, path)
|
|
body = html_response(conn, 200)
|
|
if check_title, do: assert body =~ "Users"
|
|
assert body =~ "AppLayout"
|
|
body
|
|
end
|
|
```
|
|
|
|
**Good:**
|
|
```elixir
|
|
defp goto_users_page(conn, suffix \\ "") do
|
|
path = "/users" <> suffix
|
|
conn = get(conn, path)
|
|
body = html_response(conn, 200)
|
|
assert body =~ "Users"
|
|
assert body =~ "AppLayout"
|
|
body
|
|
end
|
|
|
|
defp goto_user_page(conn, suffix) do
|
|
path = "/users" <> suffix
|
|
conn = get(conn, path)
|
|
body = html_response(conn, 200)
|
|
assert body =~ "AppLayout"
|
|
body
|
|
end
|
|
```
|
|
|
|
## Test Structure
|
|
|
|
### Context Block
|
|
|
|
```elixir
|
|
describe "resource name" do
|
|
# Shared setup in context if needed
|
|
# test "scenario" do ...
|
|
end
|
|
```
|
|
|
|
### Value Aliasing
|
|
|
|
Never reuse value names. Elixir is immutable, but value aliasing is confusing. Use unique, meaningful names for the left hand side of assignments. Or use pipes `|>` to eliminate the need for naming.
|
|
|
|
```elixir
|
|
test "GET /users returns index", %{conn: conn} do
|
|
# don't reassign
|
|
response = get(conn, "/users")
|
|
# ...
|
|
end
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### HTML Pages with Layout
|
|
|
|
```elixir
|
|
defp goto_resource_page(conn, suffix \\ ""), do: ...
|
|
# Asserts common layout elements and page-specific content
|
|
```
|
|
|
|
### API Endpoints
|
|
|
|
```elixir
|
|
test "GET /api/resource returns JSON", %{conn: conn} do
|
|
conn = conn |> put_req_header("accept", "application/json")
|
|
conn = get(conn, "/api/resource")
|
|
response = json_response(conn, 200)
|
|
# Assert structure
|
|
end
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```elixir
|
|
test "returns 404 for nonexistent", %{conn: conn} do
|
|
assert html_response(get(conn, "/nonexistent"), 404)
|
|
end
|
|
```
|
|
|
|
## Running Tests
|
|
|
|
### One focused test file
|
|
|
|
```bash
|
|
cd /path/to/app
|
|
mix test test/path/to_test.exs
|
|
```
|
|
|
|
### all tests
|
|
```bash
|
|
make test
|
|
```
|