Skip to main content

test_that/matchers/
display_matcher.rs

1// Copyright 2022 Google LLC
2// Copyright 2026 Bradford Hovinen <bradford@hovinen.me>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::description::Description;
17use crate::matcher::{Describable, Matcher, MatcherResult};
18use alloc::string::String;
19use core::fmt::{Debug, Display};
20
21/// Matches the string representation of types that implement `Display`.
22///
23/// ```
24/// # use test_that::prelude::*;
25/// let result = "Hello, world!";
26/// assert_that!(result, displays_as(eq("Hello, world!")));
27/// ```
28///
29/// Use [alternate()][DisplayMatcher::alternate] to match against the value
30/// rendered with the alternate formatter `{:#}`.
31///
32/// ```
33/// # use test_that::prelude::*;
34/// let result = "Hello, world!";
35/// assert_that!(result, displays_as(eq("Hello, world!")).alternate());
36/// ```
37pub fn displays_as<InnerMatcher>(inner: InnerMatcher) -> DisplayMatcher<InnerMatcher> {
38    DisplayMatcher { inner, alternate: false }
39}
40
41/// A matcher which renders the actual value as a `String` with `Display` and
42/// matches the result against the given inner matcher.
43pub struct DisplayMatcher<InnerMatcher> {
44    inner: InnerMatcher,
45    alternate: bool,
46}
47
48impl<InnerMatcher> DisplayMatcher<InnerMatcher> {
49    /// Indicates that the match should be made against the alternate rendering
50    /// of the actual value. That is the result of formatting it with
51    /// `{:#}`.
52    pub fn alternate(mut self) -> Self {
53        self.alternate = true;
54        self
55    }
56}
57
58impl<T: Debug + Display + ?Sized, InnerMatcher: Matcher<String>> Matcher<T>
59    for DisplayMatcher<InnerMatcher>
60{
61    fn matches(&self, actual: &T) -> MatcherResult {
62        let rendered = if self.alternate { format!("{actual:#}") } else { format!("{actual}") };
63        self.inner.matches(&rendered)
64    }
65
66    fn explain_match(&self, actual: &T) -> Description {
67        let rendered = if self.alternate { format!("{actual:#}") } else { format!("{actual}") };
68        let inner_explanation = self.inner.explain_match(&rendered);
69        Description::new()
70            .text("which displays as:")
71            .nested(Description::from(rendered).indent())
72            .append(inner_explanation)
73    }
74}
75
76impl<InnerMatcher: Describable> Describable for DisplayMatcher<InnerMatcher> {
77    fn describe(&self, matcher_result: MatcherResult) -> Description {
78        match matcher_result {
79            MatcherResult::Match => {
80                format!("displays as a string which {}", self.inner.describe(MatcherResult::Match))
81                    .into()
82            }
83            MatcherResult::NoMatch => format!(
84                "doesn't display as a string which {}",
85                self.inner.describe(MatcherResult::Match)
86            )
87            .into(),
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::displays_as;
95    use crate::prelude::*;
96    use core::fmt::{Debug, Display, Error, Formatter};
97    use indoc::indoc;
98    use serial_test::serial;
99
100    #[test]
101    fn display_matches_i32() -> TestResult<()> {
102        let value = 32;
103        verify_that!(value, displays_as(eq("32")))?;
104        Ok(())
105    }
106
107    #[test]
108    fn display_matches_str() -> TestResult<()> {
109        let value = "32";
110        verify_that!(value, displays_as(eq("32")))?;
111        Ok(())
112    }
113
114    #[test]
115    fn display_matches_struct() -> TestResult<()> {
116        #[allow(dead_code)]
117        #[derive(Debug)]
118        struct Struct {
119            a: i32,
120            b: i64,
121        }
122        impl Display for Struct {
123            fn fmt(&self, f: &mut Formatter<'_>) -> core::result::Result<(), Error> {
124                write!(f, "{:?}", self)
125            }
126        }
127        verify_that!(Struct { a: 123, b: 321 }, displays_as(eq("Struct { a: 123, b: 321 }")))?;
128        Ok(())
129    }
130
131    #[test]
132    fn display_matches_struct_with_alternate_rendering() -> TestResult<()> {
133        #[derive(Debug)]
134        struct Struct;
135        impl Display for Struct {
136            fn fmt(&self, f: &mut Formatter<'_>) -> core::result::Result<(), Error> {
137                if f.alternate() { write!(f, "Correct") } else { write!(f, "Not correct") }
138            }
139        }
140
141        verify_that!(Struct, displays_as(eq("Correct")).alternate())
142    }
143
144    #[cfg(feature = "std")]
145    #[test]
146    #[serial]
147    fn display_displays_error_message_with_explanation_from_inner_matcher() -> TestResult<()> {
148        let result = verify_that!("123\n234", displays_as(eq("123\n345")));
149
150        verify_that!(
151            result,
152            err(displays_as(contains_substring(indoc!(
153                r#"
154                  Actual: "123\n234",
155                    which displays as:
156                      123
157                      234
158                    which isn't equal to "123\n345"
159                    Difference(-actual / +expected):
160                     123
161                    -234
162                    +345
163                "#
164            ))))
165        )
166    }
167
168    #[cfg(feature = "std")]
169    #[test]
170    #[serial]
171    fn display_puts_alternate_rendering_in_explanation_when_requested() -> TestResult<()> {
172        #[derive(Debug)]
173        struct Struct;
174        impl Display for Struct {
175            fn fmt(&self, f: &mut Formatter<'_>) -> core::result::Result<(), Error> {
176                if f.alternate() { write!(f, "Correct") } else { write!(f, "Not correct") }
177            }
178        }
179
180        let result = verify_that!(Struct, displays_as(eq("Not correct")).alternate());
181
182        verify_that!(
183            result,
184            err(displays_as(contains_substring(indoc!(
185                r#"
186                  Actual: Struct,
187                    which displays as:
188                      Correct
189                    which isn't equal to "Not correct"
190                "#
191            ))))
192        )
193    }
194}