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

sunng87 / handlebars-rust / 29343207958

12 Jul 2026 07:59AM UTC coverage: 84.495% (-0.6%) from 85.052%
29343207958

push

github

web-flow
test: add shared TestHandlebars helpers and adopt across render tests (#771)

Introduce an off-by-default `testing` cargo feature exposing a
`#[doc(hidden)] pub mod testing` with a `TestHandlebars` extension trait
over `Registry`: `assert_render_template`, `assert_render`, `register`,
`assert_render_template_ok`, `assert_render_template_err`, and
`assert_render_err`. Method names mirror the underlying `Registry` calls
1:1. The module is compiled for this crate's own tests via a self
dev-dependency, and is also available to downstream users testing custom
helpers/templates. On failure, assertions print the template and the data
so the cause is obvious without a debugger.

Adopt the trait across the render-assert tests in `tests/` and the
`src/` unit-test modules, removing ~750 lines of
`register_template_string(..).is_ok()` / `.unwrap()` / `assert_eq!`
boilerplate. `helper_macro` becomes table-driven. Low-level tests
(template AST, grammar parser, context navigation, render-of-elements,
and the detailed error-inspection tests in registry/render) are
intentionally left untouched.

Two pre-existing issues fixed during the pass:
- `helper_each::test_nested_each_with_path_ups` was missing its `#[test]`
  attribute (dead code); it now runs and passes.
- `partial::teset_partial_context_with_both_hash_and_param` typo renamed
  to `test_...`.

26 of 44 new or added lines in 2 files covered. (59.09%)

1733 of 2051 relevant lines covered (84.5%)

8.05 hits per line

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

57.14
/src/testing.rs
1
//! Ergonomic test helpers for handlebars.
2
//!
3
//! Available to this crate's own `#[cfg(test)]` modules unconditionally, and
4
//! to downstream users (for testing their own helpers/templates) when the
5
//! `testing` cargo feature is enabled.
6
//!
7
//! The helpers collapse the repetitive `Registry::new()` / `render_template` /
8
//! `.unwrap()` / `assert_eq!` boilerplate and, more importantly, produce
9
//! self-describing failure messages that include the template and the data.
10
//!
11
//! ```ignore
12
//! use handlebars::Handlebars;
13
//! use handlebars::testing::TestHandlebars;
14
//! use serde_json::json;
15
//!
16
//! // inline string template
17
//! let hbs = Handlebars::new();
18
//! hbs.assert_render_template("hello {{name}}", &json!({"name": "world"}), "hello world");
19
//!
20
//! // registered template, rendered by name
21
//! let mut hbs = Handlebars::new();
22
//! hbs.register("p", "{{this}}!");
23
//! hbs.assert_render("p", &json!("hi"), "hi!");
24
//! ```
25

26
use crate::error::RenderError;
27
use crate::registry::Registry;
28
use serde::Serialize;
29

30
/// Extension trait that adds render-test assertions to a [`Registry`].
31
///
32
/// On failure these methods panic with the template and the data so the cause
33
/// is obvious without re-running under a debugger.
34
pub trait TestHandlebars {
35
    /// Register a template string, panicking with a descriptive message on a
36
    /// compile error.
37
    ///
38
    /// Replaces the common `assert!(hbs.register_template_string(..).is_ok())`
39
    /// idiom, which hides compile errors.
40
    fn register(&mut self, name: &str, template: &str);
41

42
    /// Render an inline string template against `data` and assert the output
43
    /// equals `expected`.
44
    fn assert_render_template<T: Serialize>(&self, template: &str, data: &T, expected: &str);
45

46
    /// Render a previously-registered template by `name` and assert the output
47
    /// equals `expected`.
48
    fn assert_render<T: Serialize>(&self, name: &str, data: &T, expected: &str);
49

50
    /// Assert that rendering an inline template succeeds (the output is
51
    /// discarded). Useful for suites that toggle registry options such as
52
    /// strict mode between cases.
53
    fn assert_render_template_ok<T: Serialize>(&self, template: &str, data: &T);
54

55
    /// Assert that rendering an inline template fails. If `msg` is `Some(_)`,
56
    /// additionally require the rendered error string to contain it.
57
    ///
58
    /// The error is returned so the caller can inspect it further
59
    /// (e.g. `reason()`, `line_no`).
60
    fn assert_render_template_err<T: Serialize>(
61
        &self,
62
        template: &str,
63
        data: &T,
64
        msg: Option<&str>,
65
    ) -> RenderError;
66

67
    /// Assert that rendering a registered template by `name` fails. If `msg` is
68
    /// `Some(_)`, additionally require the error string to contain it. Returns
69
    /// the error for further inspection.
70
    fn assert_render_err<T: Serialize>(
71
        &self,
72
        name: &str,
73
        data: &T,
74
        msg: Option<&str>,
75
    ) -> RenderError;
76
}
77

78
impl<'reg> TestHandlebars for Registry<'reg> {
79
    fn register(&mut self, name: &str, template: &str) {
9✔
80
        if let Err(e) = self.register_template_string(name, template) {
9✔
NEW
81
            panic!("failed to register template {name:?} ({template:?}):\n{e}");
×
82
        }
83
    }
84

85
    fn assert_render_template<T: Serialize>(&self, template: &str, data: &T, expected: &str) {
21✔
86
        let actual = self.render_template(template, data).unwrap_or_else(|e| {
20✔
NEW
87
            panic!("render_template failed:\n  template: {template:?}\n  error: {e}")
×
88
        });
89
        assert_eq!(
40✔
NEW
90
            actual,
×
NEW
91
            expected,
×
92
            "\nrender_template mismatch\n  template: {template:?}\n  data: {}\n",
NEW
93
            data_to_string(data)
×
94
        );
95
    }
96

97
    fn assert_render<T: Serialize>(&self, name: &str, data: &T, expected: &str) {
17✔
98
        let actual = self
14✔
99
            .render(name, data)
17✔
100
            .unwrap_or_else(|e| panic!("render({name:?}) failed:\n  error: {e}"));
15✔
101
        assert_eq!(
29✔
NEW
102
            actual,
×
NEW
103
            expected,
×
104
            "\nrender({name:?}) mismatch\n  data: {}\n",
NEW
105
            data_to_string(data)
×
106
        );
107
    }
108

109
    fn assert_render_template_ok<T: Serialize>(&self, template: &str, data: &T) {
2✔
110
        if let Err(e) = self.render_template(template, data) {
2✔
NEW
111
            panic!(
×
112
                "expected render_template to succeed, but it errored:\n  template: {template:?}\n  error: {e}"
113
            );
114
        }
115
    }
116

117
    fn assert_render_template_err<T: Serialize>(
4✔
118
        &self,
119
        template: &str,
120
        data: &T,
121
        msg: Option<&str>,
122
    ) -> RenderError {
123
        match self.render_template(template, data) {
4✔
NEW
124
            Ok(actual) => panic!(
×
125
                "expected render_template to fail, but it produced:\n  template: {template:?}\n  output: {actual:?}"
126
            ),
127
            Err(e) => {
4✔
128
                if let Some(needle) = msg {
5✔
129
                    let s = e.to_string();
1✔
NEW
130
                    assert!(
×
131
                        s.contains(needle),
2✔
132
                        "render_template failed as expected, but the error did not contain \
133
                         {needle:?}:\n  {s}"
134
                    );
135
                }
136
                e
4✔
137
            }
138
        }
139
    }
140

141
    fn assert_render_err<T: Serialize>(
2✔
142
        &self,
143
        name: &str,
144
        data: &T,
145
        msg: Option<&str>,
146
    ) -> RenderError {
147
        match self.render(name, data) {
2✔
NEW
148
            Ok(actual) => {
×
NEW
149
                panic!("expected render({name:?}) to fail, but it produced:\n  output: {actual:?}")
×
150
            }
151
            Err(e) => {
2✔
152
                if let Some(needle) = msg {
2✔
NEW
153
                    let s = e.to_string();
×
NEW
154
                    assert!(
×
NEW
155
                        s.contains(needle),
×
156
                        "render({name:?}) failed as expected, but the error did not contain \
157
                         {needle:?}:\n  {s}"
158
                    );
159
                }
160
                e
2✔
161
            }
162
        }
163
    }
164
}
165

NEW
166
fn data_to_string<T: Serialize>(data: &T) -> String {
×
NEW
167
    serde_json::to_string(data).unwrap_or_else(|_| "<non-serializable>".to_owned())
×
168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc