Skip to content

Commit 76d5376

Browse files
committed
feat(assert): add JSON assertions with jq support
1 parent c038c02 commit 76d5376

5 files changed

Lines changed: 206 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66
- Add `assert_have_been_called_nth_with` for verifying arguments on the Nth invocation of a spy
77
- Add `assert_string_matches_format` and `assert_string_not_matches_format` with format placeholders (`%d`, `%s`, `%f`, `%i`, `%x`, `%e`, `%%`)
8+
- Add JSON assertions: `assert_json_key_exists`, `assert_json_contains`, `assert_json_equals` (requires `jq`)
89

910
### Changed
1011
- Split Windows CI test jobs into parallel chunks to avoid timeouts

docs/assertions.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,60 @@ function test_failure() {
12161216
```
12171217
:::
12181218
1219+
## assert_json_key_exists
1220+
> `assert_json_key_exists "key" "json"`
1221+
1222+
Reports an error if `key` does not exist in the JSON string. Uses [jq](https://jqlang.github.io/jq/) syntax for key paths. Requires `jq` to be installed; if missing the test is skipped.
1223+
1224+
::: code-group
1225+
```bash [Example]
1226+
function test_success() {
1227+
assert_json_key_exists ".name" '{"name":"bashunit","version":"1.0"}'
1228+
assert_json_key_exists ".data.id" '{"data":{"id":42}}'
1229+
}
1230+
1231+
function test_failure() {
1232+
assert_json_key_exists ".missing" '{"name":"bashunit"}'
1233+
}
1234+
```
1235+
:::
1236+
1237+
## assert_json_contains
1238+
> `assert_json_contains "key" "expected" "json"`
1239+
1240+
Reports an error if `key` does not exist in the JSON string or its value does not equal `expected`. Uses [jq](https://jqlang.github.io/jq/) syntax for key paths. Requires `jq` to be installed; if missing the test is skipped.
1241+
1242+
::: code-group
1243+
```bash [Example]
1244+
function test_success() {
1245+
assert_json_contains ".name" "bashunit" '{"name":"bashunit","version":"1.0"}'
1246+
assert_json_contains ".count" "42" '{"count":42}'
1247+
}
1248+
1249+
function test_failure() {
1250+
assert_json_contains ".name" "other" '{"name":"bashunit"}'
1251+
assert_json_contains ".missing" "value" '{"name":"bashunit"}'
1252+
}
1253+
```
1254+
:::
1255+
1256+
## assert_json_equals
1257+
> `assert_json_equals "expected" "actual"`
1258+
1259+
Reports an error if the two JSON strings are not structurally equal. Key order is ignored. Requires `jq` to be installed; if missing the test is skipped.
1260+
1261+
::: code-group
1262+
```bash [Example]
1263+
function test_success() {
1264+
assert_json_equals '{"b":2,"a":1}' '{"a":1,"b":2}'
1265+
}
1266+
1267+
function test_failure() {
1268+
assert_json_equals '{"a":1}' '{"a":2}'
1269+
}
1270+
```
1271+
:::
1272+
12191273
## bashunit::fail
12201274
> `bashunit::fail "failure message"`
12211275

src/assert_json.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env bash
2+
3+
function bashunit::assert_json::require_jq() {
4+
if ! command -v jq >/dev/null 2>&1; then
5+
bashunit::skip "jq is required for JSON assertions"
6+
return 1
7+
fi
8+
return 0
9+
}
10+
11+
function assert_json_key_exists() {
12+
bashunit::assert::should_skip && return 0
13+
bashunit::assert_json::require_jq || return 0
14+
15+
local key="$1"
16+
local json="$2"
17+
18+
local result
19+
if ! result=$(printf '%s' "$json" | jq -e "$key" 2>/dev/null) || [ "$result" = "null" ]; then
20+
local test_fn
21+
test_fn="$(bashunit::helper::find_test_function_name)"
22+
local label
23+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
24+
bashunit::assert::mark_failed
25+
bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}"
26+
return
27+
fi
28+
29+
bashunit::state::add_assertions_passed
30+
}
31+
32+
function assert_json_contains() {
33+
bashunit::assert::should_skip && return 0
34+
bashunit::assert_json::require_jq || return 0
35+
36+
local key="$1"
37+
local expected="$2"
38+
local json="$3"
39+
40+
local result
41+
if ! result=$(printf '%s' "$json" | jq -e -r "$key" 2>/dev/null) || [ "$result" = "null" ]; then
42+
local test_fn
43+
test_fn="$(bashunit::helper::find_test_function_name)"
44+
local label
45+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
46+
bashunit::assert::mark_failed
47+
bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}"
48+
return
49+
fi
50+
51+
if [ "$result" != "$expected" ]; then
52+
local test_fn
53+
test_fn="$(bashunit::helper::find_test_function_name)"
54+
local label
55+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
56+
bashunit::assert::mark_failed
57+
bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${result}"
58+
return
59+
fi
60+
61+
bashunit::state::add_assertions_passed
62+
}
63+
64+
function assert_json_equals() {
65+
bashunit::assert::should_skip && return 0
66+
bashunit::assert_json::require_jq || return 0
67+
68+
local expected="$1"
69+
local actual="$2"
70+
71+
local expected_sorted
72+
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
73+
local actual_sorted
74+
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
75+
76+
if [ "$expected_sorted" != "$actual_sorted" ]; then
77+
local test_fn
78+
test_fn="$(bashunit::helper::find_test_function_name)"
79+
local label
80+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
81+
bashunit::assert::mark_failed
82+
bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${actual}"
83+
return
84+
fi
85+
86+
bashunit::state::add_assertions_passed
87+
}

src/assertions.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ source "$BASHUNIT_ROOT_DIR/src/assert.sh"
44
source "$BASHUNIT_ROOT_DIR/src/assert_arrays.sh"
55
source "$BASHUNIT_ROOT_DIR/src/assert_files.sh"
66
source "$BASHUNIT_ROOT_DIR/src/assert_folders.sh"
7+
source "$BASHUNIT_ROOT_DIR/src/assert_json.sh"
78
source "$BASHUNIT_ROOT_DIR/src/assert_snapshot.sh"
89
source "$BASHUNIT_ROOT_DIR/src/skip_todo.sh"
910
source "$BASHUNIT_ROOT_DIR/src/test_doubles.sh"

tests/unit/assert_json_test.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# shellcheck disable=SC2329
3+
4+
function test_successful_assert_json_key_exists() {
5+
assert_empty "$(assert_json_key_exists ".name" '{"name":"bashunit","version":"1.0"}')"
6+
}
7+
8+
function test_successful_assert_json_key_exists_nested() {
9+
assert_empty "$(assert_json_key_exists ".data.id" '{"data":{"id":42}}')"
10+
}
11+
12+
function test_unsuccessful_assert_json_key_exists() {
13+
local json='{"name":"bashunit"}'
14+
15+
assert_same \
16+
"$(bashunit::console_results::print_failed_test \
17+
"Unsuccessful assert json key exists" \
18+
"$json" "to have key" ".missing")" \
19+
"$(assert_json_key_exists ".missing" "$json")"
20+
}
21+
22+
function test_successful_assert_json_contains() {
23+
assert_empty "$(assert_json_contains ".name" "bashunit" '{"name":"bashunit","version":"1.0"}')"
24+
}
25+
26+
function test_successful_assert_json_contains_numeric() {
27+
assert_empty "$(assert_json_contains ".count" "42" '{"count":42}')"
28+
}
29+
30+
function test_unsuccessful_assert_json_contains_wrong_value() {
31+
local json='{"name":"bashunit"}'
32+
33+
assert_same \
34+
"$(bashunit::console_results::print_failed_test \
35+
"Unsuccessful assert json contains wrong value" \
36+
"other" "but got " "bashunit")" \
37+
"$(assert_json_contains ".name" "other" "$json")"
38+
}
39+
40+
function test_unsuccessful_assert_json_contains_missing_key() {
41+
local json='{"name":"bashunit"}'
42+
43+
assert_same \
44+
"$(bashunit::console_results::print_failed_test \
45+
"Unsuccessful assert json contains missing key" \
46+
"$json" "to have key" ".missing")" \
47+
"$(assert_json_contains ".missing" "value" "$json")"
48+
}
49+
50+
function test_successful_assert_json_equals() {
51+
assert_empty "$(assert_json_equals '{"b":2,"a":1}' '{"a":1,"b":2}')"
52+
}
53+
54+
function test_unsuccessful_assert_json_equals() {
55+
local expected='{"a":1}'
56+
local actual='{"a":2}'
57+
58+
assert_same \
59+
"$(bashunit::console_results::print_failed_test \
60+
"Unsuccessful assert json equals" \
61+
"$expected" "but got " "$actual")" \
62+
"$(assert_json_equals "$expected" "$actual")"
63+
}

0 commit comments

Comments
 (0)