• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alexferl / zerohttp / 23517605997

24 Mar 2026 11:42PM UTC coverage: 92.85% (-0.6%) from 93.49%
23517605997

Pull #145

github

web-flow
Merge da410e4f1 into b5b4082bb
Pull Request #145: feat(zhtest): add general assertion helpers

96 of 169 new or added lines in 1 file covered. (56.8%)

7 existing lines in 2 files now uncovered.

9571 of 10308 relevant lines covered (92.85%)

441.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.91
/zhtest/assert.go
1
package zhtest
2

3
import (
4
        "encoding/json"
5
        "errors"
6
        "fmt"
7
        "net/http/httptest"
8
        "reflect"
9
        "strings"
10
        "testing"
11

12
        "github.com/alexferl/zerohttp/httpx"
13
)
14

15
// Assert creates a new Assertions instance for the given ResponseRecorder.
16
// This is a convenience function that doesn't require passing *testing.T.
17
// For automatic test failures, use AssertWith.
18
//
19
// Example:
20
//
21
//        w := httptest.NewRecorder()
22
//        router.ServeHTTP(w, req)
23
//        zhtest.Assert(w).Status(http.StatusOK)
24
func Assert(w *httptest.ResponseRecorder) *Assertions {
81✔
25
        return &Assertions{resp: &Response{w}, t: nil}
81✔
26
}
81✔
27

28
// AssertWith creates a new Assertions instance that will automatically fail the test.
29
//
30
// Example:
31
//
32
//        w := httptest.NewRecorder()
33
//        router.ServeHTTP(w, req)
34
//        zhtest.AssertWith(t, w).Status(http.StatusOK)
35
func AssertWith(t *testing.T, w *httptest.ResponseRecorder) *Assertions {
1✔
36
        return &Assertions{resp: &Response{w}, t: t}
1✔
37
}
1✔
38

39
// Assertions provides fluent assertion methods for HTTP responses.
40
type Assertions struct {
41
        resp *Response
42
        t    *testing.T
43
}
44

45
// fail reports a test failure if a testing.T is available.
46
func (a *Assertions) fail(format string, args ...any) {
49✔
47
        if a.t != nil {
49✔
48
                a.t.Errorf(format, args...)
×
49
        }
×
50
}
51

52
// Status asserts that the response status code equals the expected value.
53
//
54
// Example:
55
//
56
//        zhtest.AssertWith(t, w).Status(http.StatusOK)
57
func (a *Assertions) Status(expected int) *Assertions {
4✔
58
        if a.resp.Code != expected {
5✔
59
                a.fail("expected status %d, got %d", expected, a.resp.Code)
1✔
60
        }
1✔
61
        return a
4✔
62
}
63

64
// StatusNot asserts that the response status code does not equal the given value.
65
//
66
// Example:
67
//
68
//        zhtest.AssertWith(t, w).StatusNot(http.StatusNotFound)
69
func (a *Assertions) StatusNot(unexpected int) *Assertions {
2✔
70
        if a.resp.Code == unexpected {
3✔
71
                a.fail("expected status not to be %d, but it was", unexpected)
1✔
72
        }
1✔
73
        return a
2✔
74
}
75

76
// StatusBetween asserts that the response status code is within the given range (inclusive).
77
//
78
// Example:
79
//
80
//        zhtest.AssertWith(t, w).StatusBetween(200, 299)
81
func (a *Assertions) StatusBetween(min, max int) *Assertions {
2✔
82
        if a.resp.Code < min || a.resp.Code > max {
3✔
83
                a.fail("expected status between %d and %d, got %d", min, max, a.resp.Code)
1✔
84
        }
1✔
85
        return a
2✔
86
}
87

88
// Header asserts that the response header with the given key equals the expected value.
89
// Only checks the first value if multiple values exist.
90
//
91
// Example:
92
//
93
//        zhtest.AssertWith(t, w).httpx.HeaderContentType, "application/json")
94
func (a *Assertions) Header(key, expected string) *Assertions {
3✔
95
        actual := a.resp.Header().Get(key)
3✔
96
        if actual != expected {
4✔
97
                a.fail("expected header %q to be %q, got %q", key, expected, actual)
1✔
98
        }
1✔
99
        return a
3✔
100
}
101

102
// HeaderContains asserts that the response header with the given key contains the substring.
103
//
104
// Example:
105
//
106
//        zhtest.AssertWith(t, w).HeaderContains(httpx.HeaderContentType, "json")
107
func (a *Assertions) HeaderContains(key, substring string) *Assertions {
2✔
108
        actual := a.resp.Header().Get(key)
2✔
109
        if !strings.Contains(actual, substring) {
3✔
110
                a.fail("expected header %q to contain %q, got %q", key, substring, actual)
1✔
111
        }
1✔
112
        return a
2✔
113
}
114

115
// HeaderExists asserts that the response header with the given key is present.
116
//
117
// Example:
118
//
119
//        zhtest.AssertWith(t, w).HeaderExists("X-Request-ID")
120
func (a *Assertions) HeaderExists(key string) *Assertions {
2✔
121
        if a.resp.Header().Get(key) == "" {
3✔
122
                a.fail("expected header %q to exist, but it was missing or empty", key)
1✔
123
        }
1✔
124
        return a
2✔
125
}
126

127
// HeaderNotExists asserts that the response header with the given key is not present.
128
//
129
// Example:
130
//
131
//        zhtest.AssertWith(t, w).HeaderNotExists("X-Powered-By")
132
func (a *Assertions) HeaderNotExists(key string) *Assertions {
2✔
133
        if a.resp.Header().Get(key) != "" {
3✔
134
                a.fail("expected header %q to not exist, but it was present", key)
1✔
135
        }
1✔
136
        return a
2✔
137
}
138

139
// Body asserts that the response body equals the expected string.
140
//
141
// Example:
142
//
143
//        zhtest.AssertWith(t, w).Body("Hello, World!")
144
func (a *Assertions) Body(expected string) *Assertions {
2✔
145
        actual := a.resp.Body.String()
2✔
146
        if actual != expected {
3✔
147
                a.fail("expected body %q, got %q", expected, actual)
1✔
148
        }
1✔
149
        return a
2✔
150
}
151

152
// BodyContains asserts that the response body contains the substring.
153
//
154
// Example:
155
//
156
//        zhtest.AssertWith(t, w).BodyContains("success")
157
func (a *Assertions) BodyContains(substring string) *Assertions {
3✔
158
        actual := a.resp.Body.String()
3✔
159
        if !strings.Contains(actual, substring) {
4✔
160
                a.fail("expected body to contain %q, got %q", substring, actual)
1✔
161
        }
1✔
162
        return a
3✔
163
}
164

165
// BodyNotContains asserts that the response body does not contain the substring.
166
//
167
// Example:
168
//
169
//        zhtest.AssertWith(t, w).BodyNotContains("error")
170
func (a *Assertions) BodyNotContains(substring string) *Assertions {
2✔
171
        actual := a.resp.Body.String()
2✔
172
        if strings.Contains(actual, substring) {
3✔
173
                a.fail("expected body to not contain %q, but it did", substring)
1✔
174
        }
1✔
175
        return a
2✔
176
}
177

178
// BodyEmpty asserts that the response body is empty.
179
func (a *Assertions) BodyEmpty() *Assertions {
2✔
180
        if a.resp.Body.Len() > 0 {
3✔
181
                a.fail("expected body to be empty, got %q", a.resp.Body.String())
1✔
182
        }
1✔
183
        return a
2✔
184
}
185

186
// BodyNotEmpty asserts that the response body is not empty.
187
func (a *Assertions) BodyNotEmpty() *Assertions {
2✔
188
        if a.resp.Body.Len() == 0 {
3✔
189
                a.fail("expected body to not be empty")
1✔
190
        }
1✔
191
        return a
2✔
192
}
193

194
// JSON asserts that the response body is valid JSON and decodes it into v.
195
// This is useful when you want to decode and inspect the result.
196
//
197
// Example:
198
//
199
//        var user User
200
//        zhtest.AssertWith(t, w).JSON(&user)
201
func (a *Assertions) JSON(v any) *Assertions {
2✔
202
        if err := json.Unmarshal(a.resp.Body.Bytes(), v); err != nil {
3✔
203
                a.fail("failed to decode JSON: %v\nbody: %s", err, a.resp.Body.String())
1✔
204
        }
1✔
205
        return a
2✔
206
}
207

208
// JSONEq asserts that the response body JSON equals the expected JSON string.
209
// Both are unmarshaled and compared semantically, ignoring whitespace/formatting.
210
//
211
// Example:
212
//
213
//        zhtest.AssertWith(t, w).JSONEq(`{"name": "John"}`)
214
func (a *Assertions) JSONEq(expected string) *Assertions {
8✔
215
        var expectedVal, actualVal any
8✔
216
        if err := json.Unmarshal([]byte(expected), &expectedVal); err != nil {
8✔
217
                a.fail("failed to unmarshal expected JSON: %v", err)
×
218
                return a
×
219
        }
×
220
        if err := json.Unmarshal(a.resp.Body.Bytes(), &actualVal); err != nil {
9✔
221
                a.fail("failed to decode response JSON: %v\nbody: %s", err, a.resp.Body.String())
1✔
222
                return a
1✔
223
        }
1✔
224

225
        if !jsonValuesEqual(expectedVal, actualVal) {
13✔
226
                a.fail("expected JSON %s, got %s", expected, a.resp.Body.String())
6✔
227
        }
6✔
228
        return a
7✔
229
}
230

231
// jsonMapsEqual compares two maps for equality recursively.
232
func jsonMapsEqual(a, b map[string]any) bool {
6✔
233
        if len(a) != len(b) {
8✔
234
                return false
2✔
235
        }
2✔
236
        for k, v := range a {
8✔
237
                bv, ok := b[k]
4✔
238
                if !ok {
4✔
239
                        return false
×
240
                }
×
241
                if !jsonValuesEqual(v, bv) {
7✔
242
                        return false
3✔
243
                }
3✔
244
        }
245
        return true
1✔
246
}
247

248
// jsonValuesEqual compares two JSON values for equality.
249
func jsonValuesEqual(a, b any) bool {
14✔
250
        switch av := a.(type) {
14✔
251
        case map[string]any:
7✔
252
                bv, ok := b.(map[string]any)
7✔
253
                if !ok {
8✔
254
                        return false
1✔
255
                }
1✔
256
                return jsonMapsEqual(av, bv)
6✔
257
        case []any:
1✔
258
                bv, ok := b.([]any)
1✔
259
                if !ok {
1✔
260
                        return false
×
261
                }
×
262
                if len(av) != len(bv) {
1✔
263
                        return false
×
264
                }
×
265
                for i := range av {
4✔
266
                        if !jsonValuesEqual(av[i], bv[i]) {
4✔
267
                                return false
1✔
268
                        }
1✔
269
                }
270
                return true
×
271
        default:
6✔
272
                return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
6✔
273
        }
274
}
275

276
// JSONPathEqual asserts that the value at the given JSON path equals the expected value.
277
// Uses simple dot notation (e.g., "user.name", "items.0.id").
278
//
279
// Example:
280
//
281
//        zhtest.AssertWith(t, w).JSONPathEqual("user.name", "John")
282
func (a *Assertions) JSONPathEqual(path string, expected any) *Assertions {
9✔
283
        var data map[string]any
9✔
284
        if err := json.Unmarshal(a.resp.Body.Bytes(), &data); err != nil {
10✔
285
                a.fail("failed to decode JSON: %v\nbody: %s", err, a.resp.Body.String())
1✔
286
                return a
1✔
287
        }
1✔
288

289
        value, err := getJSONPath(data, path)
8✔
290
        if err != nil {
12✔
291
                a.fail("JSON path error: %v", err)
4✔
292
                return a
4✔
293
        }
4✔
294

295
        if fmt.Sprintf("%v", value) != fmt.Sprintf("%v", expected) {
6✔
296
                a.fail("expected JSON path %q to be %v, got %v", path, expected, value)
2✔
297
        }
2✔
298
        return a
4✔
299
}
300

301
// getJSONPath retrieves a value from a JSON structure using dot notation.
302
func getJSONPath(data map[string]any, path string) (any, error) {
8✔
303
        parts := strings.Split(path, ".")
8✔
304
        current := any(data)
8✔
305

8✔
306
        for _, part := range parts {
27✔
307
                switch v := current.(type) {
19✔
308
                case map[string]any:
15✔
309
                        next, ok := v[part]
15✔
310
                        if !ok {
16✔
311
                                return nil, fmt.Errorf("key %q not found", part)
1✔
312
                        }
1✔
313
                        current = next
14✔
314
                case []any:
3✔
315
                        // Try to parse as array index
3✔
316
                        var index int
3✔
317
                        if _, err := fmt.Sscanf(part, "%d", &index); err != nil {
4✔
318
                                return nil, fmt.Errorf("expected array index, got %q", part)
1✔
319
                        }
1✔
320
                        if index < 0 || index >= len(v) {
3✔
321
                                return nil, fmt.Errorf("index %d out of bounds", index)
1✔
322
                        }
1✔
323
                        current = v[index]
1✔
324
                default:
1✔
325
                        return nil, fmt.Errorf("cannot traverse %T", current)
1✔
326
                }
327
        }
328

329
        return current, nil
4✔
330
}
331

332
// Cookie asserts that a cookie with the given name exists and has the expected value.
333
//
334
// Example:
335
//
336
//        zhtest.AssertWith(t, w).Cookie("session", "abc123")
337
func (a *Assertions) Cookie(name, expected string) *Assertions {
3✔
338
        cookie := a.resp.Cookie(name)
3✔
339
        if cookie == nil {
4✔
340
                a.fail("expected cookie %q to exist, but it was not found", name)
1✔
341
                return a
1✔
342
        }
1✔
343
        if cookie.Value != expected {
3✔
344
                a.fail("expected cookie %q to be %q, got %q", name, expected, cookie.Value)
1✔
345
        }
1✔
346
        return a
2✔
347
}
348

349
// CookieExists asserts that a cookie with the given name exists.
350
//
351
// Example:
352
//
353
//        zhtest.AssertWith(t, w).CookieExists("session")
354
func (a *Assertions) CookieExists(name string) *Assertions {
2✔
355
        if a.resp.Cookie(name) == nil {
3✔
356
                a.fail("expected cookie %q to exist, but it was not found", name)
1✔
357
        }
1✔
358
        return a
2✔
359
}
360

361
// CookieNotExists asserts that a cookie with the given name does not exist.
362
//
363
// Example:
364
//
365
//        zhtest.AssertWith(t, w).CookieNotExists("session")
366
func (a *Assertions) CookieNotExists(name string) *Assertions {
2✔
367
        if a.resp.Cookie(name) != nil {
3✔
368
                a.fail("expected cookie %q to not exist, but it was found", name)
1✔
369
        }
1✔
370
        return a
2✔
371
}
372

373
// Redirect asserts that the response is a redirect to the given location.
374
//
375
// Example:
376
//
377
//        zhtest.AssertWith(t, w).Redirect("/login")
378
func (a *Assertions) Redirect(location string) *Assertions {
3✔
379
        if a.resp.Code < 300 || a.resp.Code >= 400 {
4✔
380
                a.fail("expected redirect status (3xx), got %d", a.resp.Code)
1✔
381
                return a
1✔
382
        }
1✔
383
        actual := a.resp.Header().Get(httpx.HeaderLocation)
2✔
384
        if actual != location {
3✔
385
                a.fail("expected redirect to %q, got %q", location, actual)
1✔
386
        }
1✔
387
        return a
2✔
388
}
389

390
// IsSuccess asserts that the response status is 2xx.
391
func (a *Assertions) IsSuccess() *Assertions {
2✔
392
        if !a.resp.IsSuccess() {
3✔
393
                a.fail("expected success status (2xx), got %d", a.resp.Code)
1✔
394
        }
1✔
395
        return a
2✔
396
}
397

398
// IsClientError asserts that the response status is 4xx.
399
func (a *Assertions) IsClientError() *Assertions {
2✔
400
        if !a.resp.IsClientError() {
3✔
401
                a.fail("expected client error status (4xx), got %d", a.resp.Code)
1✔
402
        }
1✔
403
        return a
2✔
404
}
405

406
// IsServerError asserts that the response status is 5xx.
407
func (a *Assertions) IsServerError() *Assertions {
2✔
408
        if !a.resp.IsServerError() {
3✔
409
                a.fail("expected server error status (5xx), got %d", a.resp.Code)
1✔
410
        }
1✔
411
        return a
2✔
412
}
413

414
// IsProblemDetail asserts that the response Content-Type is application/problem+json.
415
//
416
// Example:
417
//
418
//        zhtest.AssertWith(t, w).IsProblemDetail()
419
func (a *Assertions) IsProblemDetail() *Assertions {
25✔
420
        contentType := a.resp.Header().Get(httpx.HeaderContentType)
25✔
421
        if contentType != httpx.MIMEApplicationProblemJSON {
26✔
422
                a.fail("expected Content-Type application/problem+json, got %s", contentType)
1✔
423
        }
1✔
424
        return a
25✔
425
}
426

427
// ProblemDetailStatus asserts that the response is a Problem Detail with the given status.
428
//
429
// Example:
430
//
431
//        zhtest.AssertWith(t, w).ProblemDetailStatus(400)
432
func (a *Assertions) ProblemDetailStatus(expected int) *Assertions {
4✔
433
        a.IsProblemDetail()
4✔
434

4✔
435
        var problem struct {
4✔
436
                Status int `json:"status"`
4✔
437
        }
4✔
438
        if err := a.resp.JSON(&problem); err != nil {
5✔
439
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
440
                return a
1✔
441
        }
1✔
442

443
        if problem.Status != expected {
4✔
444
                a.fail("expected Problem Detail status %d, got %d", expected, problem.Status)
1✔
445
        }
1✔
446
        return a
3✔
447
}
448

449
// ProblemDetailTitle asserts that the response is a Problem Detail with the given title.
450
//
451
// Example:
452
//
453
//        zhtest.AssertWith(t, w).ProblemDetailTitle("Bad Request")
454
func (a *Assertions) ProblemDetailTitle(expected string) *Assertions {
4✔
455
        a.IsProblemDetail()
4✔
456

4✔
457
        var problem struct {
4✔
458
                Title string `json:"title"`
4✔
459
        }
4✔
460
        if err := a.resp.JSON(&problem); err != nil {
5✔
461
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
462
                return a
1✔
463
        }
1✔
464

465
        if problem.Title != expected {
4✔
466
                a.fail("expected Problem Detail title %q, got %q", expected, problem.Title)
1✔
467
        }
1✔
468
        return a
3✔
469
}
470

471
// ProblemDetailDetail asserts that the response is a Problem Detail with the given detail message.
472
//
473
// Example:
474
//
475
//        zhtest.AssertWith(t, w).ProblemDetailDetail("The request was invalid")
476
func (a *Assertions) ProblemDetailDetail(expected string) *Assertions {
4✔
477
        a.IsProblemDetail()
4✔
478

4✔
479
        var problem struct {
4✔
480
                Detail string `json:"detail"`
4✔
481
        }
4✔
482
        if err := a.resp.JSON(&problem); err != nil {
5✔
483
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
484
                return a
1✔
485
        }
1✔
486

487
        if problem.Detail != expected {
4✔
488
                a.fail("expected Problem Detail detail %q, got %q", expected, problem.Detail)
1✔
489
        }
1✔
490
        return a
3✔
491
}
492

493
// ProblemDetailType asserts that the response is a Problem Detail with the given type URI.
494
//
495
// Example:
496
//
497
//        zhtest.AssertWith(t, w).ProblemDetailType("https://api.example.com/errors/invalid-request")
498
func (a *Assertions) ProblemDetailType(expected string) *Assertions {
4✔
499
        a.IsProblemDetail()
4✔
500

4✔
501
        var problem struct {
4✔
502
                Type string `json:"type"`
4✔
503
        }
4✔
504
        if err := a.resp.JSON(&problem); err != nil {
5✔
505
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
506
                return a
1✔
507
        }
1✔
508

509
        if problem.Type != expected {
4✔
510
                a.fail("expected Problem Detail type %q, got %q", expected, problem.Type)
1✔
511
        }
1✔
512
        return a
3✔
513
}
514

515
// ProblemDetailExtension asserts that the response is a Problem Detail with the given extension field value.
516
//
517
// Example:
518
//
519
//        zhtest.AssertWith(t, w).ProblemDetailExtension("errors", []any{"field required"})
520
func (a *Assertions) ProblemDetailExtension(key string, expected any) *Assertions {
4✔
521
        a.IsProblemDetail()
4✔
522

4✔
523
        var problem map[string]any
4✔
524
        if err := a.resp.JSON(&problem); err != nil {
5✔
525
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
526
                return a
1✔
527
        }
1✔
528

529
        value, ok := problem[key]
3✔
530
        if !ok {
4✔
531
                a.fail("expected Problem Detail extension %q to exist", key)
1✔
532
                return a
1✔
533
        }
1✔
534

535
        if fmt.Sprintf("%v", value) != fmt.Sprintf("%v", expected) {
3✔
536
                a.fail("expected Problem Detail extension %q to be %v, got %v", key, expected, value)
1✔
537
        }
1✔
538
        return a
2✔
539
}
540

541
// ProblemDetail decodes the response as a Problem Detail and stores it in v.
542
//
543
// Example:
544
//
545
//        var problem zerohttp.ProblemDetail
546
//        zhtest.AssertWith(t, w).ProblemDetail(&problem)
547
func (a *Assertions) ProblemDetail(v any) *Assertions {
2✔
548
        a.IsProblemDetail()
2✔
549

2✔
550
        if err := a.resp.JSON(v); err != nil {
3✔
551
                a.fail("failed to decode Problem Detail JSON: %v", err)
1✔
552
        }
1✔
553
        return a
2✔
554
}
555

556
// AssertNoError fails if err is not nil.
557
//
558
// Example:
559
//
560
//        err := someFunction()
561
//        zhtest.AssertNoError(t, err)
562
func AssertNoError(t testing.TB, err error) {
1✔
563
        t.Helper()
1✔
564
        if err != nil {
1✔
NEW
565
                t.Errorf("expected no error, got: %v", err)
×
NEW
566
        }
×
567
}
568

569
// AssertError fails if err is nil.
570
//
571
// Example:
572
//
573
//        err := someFunctionThatShouldFail()
574
//        zhtest.AssertError(t, err)
575
func AssertError(t testing.TB, err error) {
1✔
576
        t.Helper()
1✔
577
        if err == nil {
1✔
NEW
578
                t.Errorf("expected an error, got nil")
×
NEW
579
        }
×
580
}
581

582
// AssertErrorIs fails if err does not match the target error using errors.Is().
583
//
584
// Example:
585
//
586
//        err := os.Open("nonexistent")
587
//        zhtest.AssertErrorIs(t, err, os.ErrNotExist)
588
func AssertErrorIs(t testing.TB, err error, target error) {
1✔
589
        t.Helper()
1✔
590
        if !errors.Is(err, target) {
1✔
NEW
591
                t.Errorf("expected error to be %v, got %v", target, err)
×
NEW
592
        }
×
593
}
594

595
// AssertErrorContains fails if err is nil or its message does not contain the substring.
596
//
597
// Example:
598
//
599
//        err := errors.New("connection refused")
600
//        zhtest.AssertErrorContains(t, err, "refused")
601
func AssertErrorContains(t testing.TB, err error, substring string) {
1✔
602
        t.Helper()
1✔
603
        if err == nil {
1✔
NEW
604
                t.Errorf("expected an error containing %q, got nil", substring)
×
NEW
605
                return
×
NEW
606
        }
×
607
        if !strings.Contains(err.Error(), substring) {
1✔
NEW
608
                t.Errorf("expected error to contain %q, got %q", substring, err.Error())
×
NEW
609
        }
×
610
}
611

612
// AssertNil fails if v is not nil.
613
//
614
// Example:
615
//
616
//        var ptr *MyStruct
617
//        zhtest.AssertNil(t, ptr)
618
func AssertNil(t testing.TB, v any) {
4✔
619
        t.Helper()
4✔
620
        if v != nil {
7✔
621
                // Handle wrapped nil values (typed nil pointers, interfaces, etc.)
3✔
622
                rv := reflect.ValueOf(v)
3✔
623
                switch rv.Kind() {
3✔
624
                case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func, reflect.Interface:
3✔
625
                        if !rv.IsNil() {
3✔
NEW
626
                                t.Errorf("expected nil, got %v", v)
×
NEW
627
                        }
×
NEW
628
                default:
×
NEW
629
                        t.Errorf("expected nil, got %v", v)
×
630
                }
631
        }
632
}
633

634
// AssertNotNil fails if v is nil.
635
//
636
// Example:
637
//
638
//        result := someFunction()
639
//        zhtest.AssertNotNil(t, result)
640
func AssertNotNil(t testing.TB, v any) {
4✔
641
        t.Helper()
4✔
642
        if v == nil {
4✔
NEW
643
                t.Errorf("expected non-nil value, got nil")
×
NEW
644
                return
×
NEW
645
        }
×
646
        // Handle wrapped nil values (typed nil pointers, interfaces, etc.)
647
        rv := reflect.ValueOf(v)
4✔
648
        switch rv.Kind() {
4✔
649
        case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func, reflect.Interface:
3✔
650
                if rv.IsNil() {
3✔
NEW
651
                        t.Errorf("expected non-nil value, got nil")
×
NEW
652
                }
×
653
        }
654
}
655

656
// AssertEqual fails if expected != actual using == comparison.
657
// Works for comparable types.
658
//
659
// Example:
660
//
661
//        zhtest.AssertEqual(t, 42, result)
662
func AssertEqual(t testing.TB, expected, actual any) {
3✔
663
        t.Helper()
3✔
664
        if expected != actual {
3✔
NEW
665
                t.Errorf("expected %v, got %v", expected, actual)
×
NEW
666
        }
×
667
}
668

669
// AssertNotEqual fails if unexpected == actual using != comparison.
670
//
671
// Example:
672
//
673
//        zhtest.AssertNotEqual(t, "old", result)
674
func AssertNotEqual(t testing.TB, unexpected, actual any) {
2✔
675
        t.Helper()
2✔
676
        if unexpected == actual {
2✔
NEW
677
                t.Errorf("expected value not to be %v", unexpected)
×
NEW
678
        }
×
679
}
680

681
// AssertDeepEqual fails if expected and actual are not deeply equal using reflect.DeepEqual.
682
// Use this for slices, maps, and structs.
683
//
684
// Example:
685
//
686
//        expected := []int{1, 2, 3}
687
//        zhtest.AssertDeepEqual(t, expected, result)
688
func AssertDeepEqual(t testing.TB, expected, actual any) {
3✔
689
        t.Helper()
3✔
690
        if !reflect.DeepEqual(expected, actual) {
3✔
NEW
691
                t.Errorf("expected %v, got %v", expected, actual)
×
NEW
692
        }
×
693
}
694

695
// AssertTrue fails if condition is false.
696
//
697
// Example:
698
//
699
//        zhtest.AssertTrue(t, len(items) > 0)
700
func AssertTrue(t testing.TB, condition bool) {
3✔
701
        t.Helper()
3✔
702
        if !condition {
3✔
NEW
703
                t.Errorf("expected condition to be true")
×
NEW
704
        }
×
705
}
706

707
// AssertFalse fails if condition is true.
708
//
709
// Example:
710
//
711
//        zhtest.AssertFalse(t, len(items) == 0)
712
func AssertFalse(t testing.TB, condition bool) {
3✔
713
        t.Helper()
3✔
714
        if condition {
3✔
NEW
715
                t.Errorf("expected condition to be false")
×
NEW
716
        }
×
717
}
718

719
// AssertEmpty fails if s is not empty.
720
// Works with strings, slices, maps, and arrays.
721
//
722
// Example:
723
//
724
//        zhtest.AssertEmpty(t, "")
725
//        zhtest.AssertEmpty(t, []int{})
726
func AssertEmpty(t testing.TB, s any) {
4✔
727
        t.Helper()
4✔
728
        if !isEmpty(s) {
4✔
NEW
729
                t.Errorf("expected empty value, got %v", s)
×
NEW
730
        }
×
731
}
732

733
// AssertNotEmpty fails if s is empty.
734
// Works with strings, slices, maps, and arrays.
735
//
736
// Example:
737
//
738
//        zhtest.AssertNotEmpty(t, "hello")
739
//        zhtest.AssertNotEmpty(t, []int{1, 2, 3})
740
func AssertNotEmpty(t testing.TB, s any) {
2✔
741
        t.Helper()
2✔
742
        if isEmpty(s) {
2✔
NEW
743
                t.Errorf("expected non-empty value")
×
NEW
744
        }
×
745
}
746

747
// isEmpty checks if a value is empty.
748
func isEmpty(v any) bool {
6✔
749
        if v == nil {
6✔
NEW
750
                return true
×
NEW
751
        }
×
752
        rv := reflect.ValueOf(v)
6✔
753
        switch rv.Kind() {
6✔
754
        case reflect.String, reflect.Slice, reflect.Map, reflect.Array, reflect.Chan:
6✔
755
                return rv.Len() == 0
6✔
NEW
756
        case reflect.Ptr, reflect.Interface:
×
NEW
757
                if rv.IsNil() {
×
NEW
758
                        return true
×
NEW
759
                }
×
760
                // Dereference and check again
NEW
761
                return isEmpty(rv.Elem().Interface())
×
762
        }
NEW
763
        return false
×
764
}
765

766
// AssertLen fails if collection does not have the expected length.
767
// Works with strings, slices, maps, arrays, and channels.
768
//
769
// Example:
770
//
771
//        zhtest.AssertLen(t, []int{1, 2, 3}, 3)
772
func AssertLen(t testing.TB, collection any, expectedLen int) {
4✔
773
        t.Helper()
4✔
774
        rv := reflect.ValueOf(collection)
4✔
775
        switch rv.Kind() {
4✔
776
        case reflect.String, reflect.Slice, reflect.Map, reflect.Array, reflect.Chan:
4✔
777
                actualLen := rv.Len()
4✔
778
                if actualLen != expectedLen {
4✔
NEW
779
                        t.Errorf("expected length %d, got %d", expectedLen, actualLen)
×
NEW
780
                }
×
NEW
781
        default:
×
NEW
782
                t.Errorf("AssertLen requires a collection type, got %T", collection)
×
783
        }
784
}
785

786
// AssertContains fails if slice does not contain the element.
787
// Uses reflect.DeepEqual for comparison.
788
//
789
// Example:
790
//
791
//        zhtest.AssertContains(t, []int{1, 2, 3}, 2)
792
func AssertContains(t testing.TB, slice any, element any) {
2✔
793
        t.Helper()
2✔
794
        rv := reflect.ValueOf(slice)
2✔
795
        if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
2✔
NEW
796
                t.Errorf("AssertContains requires a slice or array, got %T", slice)
×
NEW
797
                return
×
NEW
798
        }
×
799

800
        for i := 0; i < rv.Len(); i++ {
6✔
801
                if reflect.DeepEqual(rv.Index(i).Interface(), element) {
6✔
802
                        return
2✔
803
                }
2✔
804
        }
NEW
805
        t.Errorf("expected slice to contain %v", element)
×
806
}
807

808
// AssertNotContains fails if slice contains the element.
809
// Uses reflect.DeepEqual for comparison.
810
//
811
// Example:
812
//
813
//        zhtest.AssertNotContains(t, []int{1, 2, 3}, 4)
814
func AssertNotContains(t testing.TB, slice any, element any) {
2✔
815
        t.Helper()
2✔
816
        rv := reflect.ValueOf(slice)
2✔
817
        if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
2✔
NEW
818
                t.Errorf("AssertNotContains requires a slice or array, got %T", slice)
×
NEW
819
                return
×
NEW
820
        }
×
821

822
        for i := 0; i < rv.Len(); i++ {
8✔
823
                if reflect.DeepEqual(rv.Index(i).Interface(), element) {
6✔
NEW
824
                        t.Errorf("expected slice to not contain %v", element)
×
NEW
825
                        return
×
NEW
826
                }
×
827
        }
828
}
829

830
// AssertIsType fails if actual is not of the expected type.
831
//
832
// Example:
833
//
834
//        zhtest.AssertIsType(t, (*MyError)(nil), err)
835
func AssertIsType(t testing.TB, expectedType any, actual any) {
3✔
836
        t.Helper()
3✔
837
        expectedReflectType := reflect.TypeOf(expectedType)
3✔
838
        actualReflectType := reflect.TypeOf(actual)
3✔
839

3✔
840
        // Handle nil pointer types for expected (e.g., (*MyType)(nil))
3✔
841
        if expectedReflectType != nil && expectedReflectType.Kind() == reflect.Ptr && actualReflectType != nil {
4✔
842
                // If expected is a pointer type but actual is not, compare the underlying type
1✔
843
                if actualReflectType.Kind() != reflect.Ptr {
1✔
NEW
844
                        expectedReflectType = expectedReflectType.Elem()
×
NEW
845
                }
×
846
        }
847

848
        if expectedReflectType != actualReflectType {
3✔
NEW
849
                if expectedReflectType == nil {
×
NEW
850
                        t.Errorf("expected type nil, got %v", actualReflectType)
×
NEW
851
                } else if actualReflectType == nil {
×
NEW
852
                        t.Errorf("expected type %v, got nil", expectedReflectType)
×
NEW
853
                } else {
×
NEW
854
                        t.Errorf("expected type %v, got %v", expectedReflectType, actualReflectType)
×
NEW
855
                }
×
856
        }
857
}
858

859
// AssertImplements fails if actual does not implement the interfaceType.
860
// The interfaceType should be a pointer to an interface (e.g., (*io.Reader)(nil)).
861
//
862
// Example:
863
//
864
//        zhtest.AssertImplements(t, (*io.Reader)(nil), myReader)
865
func AssertImplements(t testing.TB, interfaceType any, actual any) {
2✔
866
        t.Helper()
2✔
867
        interfaceReflectType := reflect.TypeOf(interfaceType)
2✔
868

2✔
869
        if interfaceReflectType == nil || interfaceReflectType.Kind() != reflect.Ptr || interfaceReflectType.Elem().Kind() != reflect.Interface {
2✔
NEW
870
                t.Errorf("AssertImplements requires a pointer to an interface as the first argument (e.g., (*io.Reader)(nil)), got %T", interfaceType)
×
NEW
871
                return
×
NEW
872
        }
×
873

874
        iface := interfaceReflectType.Elem()
2✔
875
        actualReflectType := reflect.TypeOf(actual)
2✔
876

2✔
877
        if actualReflectType == nil {
2✔
NEW
878
                t.Errorf("expected type to implement %v, but got nil", iface)
×
NEW
879
                return
×
NEW
880
        }
×
881

882
        if !actualReflectType.Implements(iface) {
2✔
NEW
883
                t.Errorf("expected type %v to implement %v", actualReflectType, iface)
×
NEW
884
        }
×
885
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc