My goal is to test the output of a function that goes to the standard output. So far, my best attempt is to substitute the stream with a string in the tests.
This is what I managed to achieve so far:
use std::io;
use std::fmt;
fn hello(stdout: &mut std::fmt::Write) {
writeln!(stdout, "Hello world");
}
#[test]
fn hello_test() {
let mut stdout = String::new();
// pass fake stdout when calling when testing
hello(&mut stdout);
assert_eq!(stdout, "Hello world\n".to_string());
}
fn main() {
// pass real stdout when calling from main
hello(&mut io::stdout());
}
The tests work, but unfortunately io::stdout()
does not implement the fmt::Write
trait.
What is the best solution for testing a function that writes to the standard output in Rust? Is there a way to fix my solution with strings, or should I look for an alternative?
The write!
macro expects the destination operand to implement either std::fmt::Write
or std::io::Write
. Since writeln!
delegates to write!
, this also applies to writeln!
.
The documentation for std::fmt::Write
says this:
the
io::Write
trait is favored over implementing this trait
Since Stdout
implements std::io::Write
, you should change the bounds on your code from fmt::Write
to io::Write
. Note, however, that String
doesn't implement io::Write
, since io::Write
accepts arbitrary bytes that may not be well-formed UTF-8; you can use Vec<u8>
instead.
use std::io;
fn hello(stdout: &mut io::Write) {
writeln!(stdout, "Hello world");
}
#[test]
fn hello_test() {
let mut stdout = Vec::new();
// pass fake stdout when calling when testing
hello(&mut stdout);
assert_eq!(stdout, b"Hello world\n");
}
fn main() {
// pass real stdout when calling from main
hello(&mut io::stdout());
}
For improved performance, if only one threads need to write on stdout, consider passing a StdoutLock
rather than a Stdout
to your function (with Stdout
, each write acquires and releases a lock).
If you really prefer to use std::fmt::Write
instead, then you could use an adapter struct that converts fmt::Write
calls to io::Write
calls.
use std::io;
use std::fmt;
struct WriteAdapter<W>(W);
impl<W> fmt::Write for WriteAdapter<W>
where
W: io::Write,
{
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), fmt::Error> {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}
fn hello(stdout: &mut fmt::Write) {
writeln!(stdout, "Hello world");
}
#[test]
fn hello_test() {
let mut stdout = String::new();
// pass fake stdout when calling when testing
hello(&mut stdout);
assert_eq!(stdout, "Hello world\n");
}
fn main() {
// pass real stdout when calling from main
hello(&mut WriteAdapter(io::stdout()));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With