Sometimes we are in the situation where we need to test the result of a callback that is invoked on another thread and we don’t know when this will happen.
The problem? Our test already runs out of scope before the callback gets called. In order to test such a scenario we’ll need the following:
- a test case, gtest
- code that runs in a thread, std::thread
- a std::promise
- an std::function callback
Lets assume we have a downloader that takes a while to download something, and (hopefully) reports true when it was successful:
class Downloader {
asyncDownload(std::string url, std::function<void(bool)> callback) {
std::thread downloadThread(download, std::move(callback));
downloadThread.join();
}
download(std::string url, std::function<void(bool)> callback) {
// perform download which takes a while
...
if (everythingWentWell)
callback(true);
}
};
We could start writing our test case like this:
TEST(Downloader, testDownload) {
auto callback = [](bool success) {
EXPECT_TRUE(success);
};
Downloader downloader;
downloader.download("http://foo.bar/interesting-file.jpg", std::move(callback));
}
This will leave us in a situation where the test case finishes while the Downloader is still downloading. What we need now is a way for the test case to wait until the callback is invoked, with a maximum timeout.
The way to go here is a promise with a 30s max timeout:
std::promise<void> waitGuard;
...
EXPECT_EQ(boost::future_status::ready, waitGuard.get_future().wait_for(std::chrono::seconds(30)));
When the callback gets invoked, we inform the wait guard by setting a value. The test case now looks like this:
TEST(Downloader, testDownload) {
std::promise<void> waitGuard;
auto callback = [&](bool success) {
// remember this gets called on the other thread
EXPECT_TRUE(success);
waitGuard.set_value();
};
Downloader downloader;
downloader.download("http://foo.bar/interesting-file.jpg", std::move(callback));
EXPECT_EQ(boost::future_status::ready, waitGuard.get_future().wait_for(std::chrono::seconds(30)));
}
Now, the test will wait until the download finishes and the callback gets called, but not more than 30s.