Skip to content

Commit b7a325b

Browse files
shwoopclaude
andauthored
Refactor/rstest migration (#109)
* docs: Planifications * chore: add rstest dev-dependency, derive PartialEq on InputAction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert input.rs tests to rstest parameterization (42 -> 7 functions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert theme.rs color roundtrip tests to rstest (7 -> 5 functions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: commit cargo lock * refactor: convert db.rs tests to rstest fixtures + parameterize round-trips Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert signal/client.rs tests to rstest parameterization (39 -> 32 functions) Parameterized: call messages, receipt variants, expiration variants, sticker variants, and view-once variants. Added make_resp() helper to reduce JsonRpcResponse boilerplate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert app.rs tests to rstest fixtures + parameterization - Convert test_app() to #[fixture] fn app() -> App - Update all 114 tests from #[test] + test_app() to #[rstest] + fixture injection - Parameterize 8 groups: resolve_mentions (4→1), bell_skipped (2→1), read_receipts (2→1), block_already_in_state (2→1), block_no_active (2→1), mouse_scroll (3→1), conversation_acceptance (2→1), group_menu_items (3→1), clears_attachment (2→1) - 119 → 102 test functions, all 119 test cases preserved Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: Include notes on testing in dev guid * fix: remove plans * refactor: split over-parameterized tests into separate functions Five tests used string case labels with match/unreachable!() to dispatch distinct setup logic, which obscures intent and conflates unrelated scenarios. Split each into dedicated #[rstest] functions while keeping fixture injection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: lint --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b846eaf commit b7a325b

File tree

9 files changed

+864
-1116
lines changed

9 files changed

+864
-1116
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ Cargo.lock
55
*~
66
.DS_Store
77
docs/book/
8+
docs/plans/
89
signal-tui-debug.log
910
.claude/settings.local.json

Cargo.lock

Lines changed: 126 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ base64 = "0.22"
2626
arboard = "3"
2727
open = "5"
2828
notify-rust = "4"
29+
30+
[dev-dependencies]
31+
rstest = "0.25"

docs/src/dev-guide/testing.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,68 @@ Run a single test by name:
2323
cargo test test_name
2424
```
2525

26+
## rstest
27+
28+
Tests use [rstest](https://docs.rs/rstest/) for fixtures and parameterization. The crate
29+
is declared in `[dev-dependencies]`.
30+
31+
### Fixtures
32+
33+
Two `#[fixture]` functions provide pre-built test objects:
34+
35+
- **`app()`** in `app.rs` — returns an `App` with an in-memory DB and connected state.
36+
- **`db()`** in `db.rs` — returns an in-memory `Database`.
37+
38+
To use a fixture, mark the test `#[rstest]` and add the fixture as a parameter:
39+
40+
```rust
41+
#[rstest]
42+
fn my_test(mut app: App) {
43+
app.input_buffer = "/quit".to_string();
44+
// ...
45+
}
46+
```
47+
48+
### Parameterized tests
49+
50+
When multiple tests share the same assertion logic but differ in inputs, use
51+
`#[case]` to collapse them into a single function:
52+
53+
```rust
54+
#[rstest]
55+
#[case("/quit", InputAction::Quit)]
56+
#[case("/q", InputAction::Quit)]
57+
#[case("/help", InputAction::Help)]
58+
fn command_returns_expected_action(#[case] input: &str, #[case] expected: InputAction) {
59+
assert_eq!(parse_input(input), expected);
60+
}
61+
```
62+
63+
Each `#[case]` produces a separate entry in `cargo test` output, so individual
64+
failures are easy to identify.
65+
66+
### When to use what
67+
68+
| Situation | Approach |
69+
|---|---|
70+
| Test needs an `App` or `Database` | Use the fixture (`#[rstest]` + parameter) |
71+
| 3+ tests with identical structure, different data | Parameterize with `#[case]` |
72+
| 2 tests with significantly different setup | Keep them separate |
73+
| Test doesn't need a fixture | Plain `#[test]` is fine |
74+
75+
### Best practices
76+
77+
- **Prefer `#[case]` over copy-paste.** If you're writing a new test and an existing
78+
parameterized test covers the same assertion pattern, add a `#[case]` instead of a
79+
new function.
80+
- **Keep case data simple.** Strings, numbers, and booleans work well in `#[case]`
81+
attributes. For complex types, build them inside the test body using a label parameter
82+
and `match`.
83+
- **Add a `_label` parameter** when case data alone doesn't make the purpose obvious.
84+
This shows up in test names (e.g., `my_test::case_basic`).
85+
- **Don't over-parameterize.** If merging tests requires a `match` with completely
86+
different setup logic per arm, separate tests are clearer.
87+
2688
## Test modules
2789

2890
Tests are defined as `#[cfg(test)] mod tests` blocks within each source file.

0 commit comments

Comments
 (0)