Info
-
Did you know that you can inject singletons to improve testability?
Example
template<class T>
struct singleton {
static auto& get() {
static T s{};
return s;
}
};
class api {
public:
virtual ~api() = default;
virtual auto call() const -> int { return 42; }
};
class app {
public:
auto run() -> int {
return singleton<api>::get().call();
}
};
class app_di {
public:
constexpr explicit(true) app_di(const api& api)
: api_{api}
{ }
auto run() const -> int {
return api_.call();
}
private:
const api& api_;
};
int main() {
{
app a{}; // coupled
assert(42 == a.run());
}
{
app_di a{singleton<api>::get()}; // injected
assert(42 == a.run())
}
{
struct : api {
auto call() const -> int override { return 43; }
} fake_api{};
app_di api{fake_api}; // faked
assert(43 == api.run());
}
}
Puzzle
-
Can you implement required steps and the app to satisfy feature tests?
- NOTE: Feature test must remain as authored
template <class T>
struct singleton {
static auto& get() {
static T s{};
return s;
}
};
class api {
public:
virtual ~api() = default;
virtual auto call() const -> int { return {}; }
};
class app; //TODO
int main() {
using namespace boost::ut;
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an app") = [&] {
// TODO
steps.given("I have an fake_api which returns {}") = [&](int value) {
// TODO
auto run_result = 0;
steps.when("I call run on the app") = [&] {
// TODO
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
// Feature tests must remain as authored
"app"_test = steps | R"(
Feature: Singleton
Scenario: Dependency Injection
Given I have an app
Given I have an fake_api which returns 42
When I call run on the app
Then I should get 42
Scenario: Dependency Injection
Given I have an app
Given I have an fake_api which returns 100
When I call run on the app
Then I should get 100
)";
}
Solutions
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an app") = [&] {
std::unique_ptr<app> app{};
steps.given("I have an fake_api which returns {}") = [&](int value) {
auto api = fake_api{value};
app = std::make_unique<class app>(api);
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app->run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
auto api = fake_api{value};
steps.given("I have an app") = [&] {
app app{api};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an app") = [&] {
app sut{};
steps.given("I have an fake_api which returns {}") = [&](int value) {
struct fake : api {
fake(int value) : value(value), api{} {};
int value{};
auto call() const -> int override {
return value;
}
} fake_api{ value };
singleton<api*>::get() = &fake_api;
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = sut.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
class FakeApi : public api {
using value_t = int;
public:
constexpr FakeApi(const value_t val) : value(val) {}
value_t call() const override { return value; }
private:
value_t value;
} fake_api{value};
steps.given("I have an app") = [&] {
app app{fake_api};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
struct fake_api {
auto call() const -> int { return value_; }
auto set(int i) -> void { value_ = i; }
int value_;
};
singleton<fake_api>::get().set(value);
steps.given("I have an app") = [&] {
app<fake_api> app;
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an app") = [&] {
app my_app{};
steps.given("I have an fake_api which returns {}") = [&](int value) {
singleton<fake_api>::get().value = value;
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = my_app.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
class fakeapi : public api {
public:
void set(const int value) { value_ = value; }
auto call() const -> int override { return value_; }
private:
int value_;
};
singleton<fakeapi>().get().set(value);
steps.given("I have an app") = [&] {
singleton<app>().get().set(&singleton<fakeapi>().get());
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = singleton<app>().get().run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
apiI api1(value);
steps.given("I have an app") = [&] {
app myApp(api1);
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = myApp.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Singleton") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an fake_api which returns {}") = [&](int value) {
singleton<fake_api>::get().value = value;
steps.given("I have an app") = [&] {
app a{singleton<fake_api>::get()};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = a.run();
};
steps.then("I should get {}") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};