I am faced with a situation where we use http::async_read(...)
and http::async_write(...)
internally in a class that represents an HTTP session. However, if we want to unit test this class, we would prefer not to open a network connection, with a port and a socket to listen on and correspondingly accept the first call via an acceptor
.
Rather, I was hoping to "fake" the behavior of the socket
via another AsyncReadStream
. I've looked into writing my own, but it seems like a difficult task and search did not reveal much help.
What is an example of an AsyncReadStream
that could be useful here to simply stream in an HTTP
request and subsequently put the response into, for the purposes of unit testing? If none, s there a template of some sort that I could use to write a barebones one with this very minimal "fake" functionality?
It's almost trivial to model the AsyncReadStream/AsyncWriteStream concepts
The only complexity is from initiating async operations using CompletionToken patterns:
E.g.:
#include <boost/asio.hpp>
#include <iostream>
#include <fmt/ranges.h>
namespace asio = boost::asio;
using namespace std::literals;
struct MockStream {
using error_code = boost::system::error_code;
using executor_type = asio::any_io_executor;
MockStream(asio::any_io_executor ex) : ex_(std::move(ex)) {}
executor_type get_executor() const { return ex_; }
template <typename Token> auto async_write_some(asio::const_buffer buf, Token&& token) {
return asio::async_initiate<Token, void(error_code, size_t)>( //
[fallback = ex_](auto h, auto buf) {
auto ex = asio::get_associated_executor(h, fallback);
asio::dispatch(ex, [=, h = std::move(h)]() mutable {
fmt::print("Simulated write of {} completed\n", buf.size());
std::move(h)({}, asio::buffer_size(buf));
});
},
token, buf);
}
template <typename Token> auto async_read_some(asio::mutable_buffer buf, Token&& token) {
return asio::async_initiate<Token, void(error_code, size_t)>( //
[fallback = ex_, ch = mock_data_++](auto h, auto buf) {
auto ex = asio::get_associated_executor(h, fallback);
std::fill(asio::buffers_begin(buf), asio::buffers_end(buf), ch);
asio::dispatch(ex, [=, h = std::move(h)]() mutable {
if (ch == 'e') {
auto partial = (rand() % buf.size()) / 4;
fmt::print("Simulated EOF with {} partial read\n", partial);
std::move(h)(asio::error::eof, partial);
} else {
fmt::print("Simulated read of {} completed\n", buf.size());
std::move(h)({}, asio::buffer_size(buf));
}
});
},
token, buf);
}
asio::any_io_executor ex_;
char mock_data_ = 'a';
};
int main() {
srand(time(NULL));
asio::io_context ioc;
MockStream stream(asio::system_executor{});
asio::async_write(stream, asio::buffer("Hello world"sv), [](auto ec, size_t n) {
fmt::print("Write completed with {} and {} bytes written\n", ec.message(), n);
});
std::array<char, 10> buf;
asio::async_read(stream, asio::buffer(buf), [&buf](auto ec, size_t n) {
fmt::print("Read completed with {} and {} bytes read: {}\n", ec.message(), n, buf);
});
asio::streambuf sbuf;
asio::async_read_until(stream, sbuf, "d", [&sbuf](auto ec, size_t n) {
fmt::print("Read-until completed with {} and {} bytes read\n", ec.message(), n);
// std::cout << "Streambuf: " << &sbuf << std::endl;
sbuf.consume(sbuf.size());
});
asio::async_read_until(stream, sbuf, "z", [&sbuf](auto ec, size_t n) {
fmt::print("Read-until completed with {} and {} bytes read\n", ec.message(), n);
std::cout << "Streambuf: " << &sbuf << std::endl;
});
ioc.run();
}
Printing e.g.
Similar implementations here: