Info
-
Did you know that with Automatic DI one can control how dependencies are being created?
-
https://boost-ext.github.io/di/user_guide.html#configuration
Example
class app {
public:
explicit(true) app(int, double) { }
};
struct config : boost::di::config {
struct mocks {
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, TArgs&&... args) const
-> boost::di::aux::owner<T*> {
std::clog << typeid(T).name() << '\n';
return new T{args...};
}
};
auto provider(...) const { return mocks{}; }
};
int main() {
boost::di::create<app>(boost::di::make_injector<config>()); // prints app
int
double
}
Puzzle
- Can you implement Automatic Mocks Provider which will create mocks for polymorhpic types (using FakeIt)?
struct mocks_provider : boost::di::config {
struct mocks {
template <class...> using is_creatable = std::true_type;
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, TArgs&&... args) const
-> boost::di::aux::owner<T*>; // TODO, creates mocks for polymorphic types
template<class T> auto& mock(); // TODO, returns mock for a given type
};
auto provider(...) const { return mocks{}; }
};
int main() {
class iapi {
public:
virtual ~iapi() = default;
virtual auto call() const -> int = 0;
};
class app {
public:
constexpr explicit(true) app(const iapi& api) : api_{api} {}
constexpr auto run() -> int { return api_.call(); }
private:
const iapi& api_;
};
using namespace boost::ut;
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Automatic Mocks Injection*") = [&] {
steps.scenario("*") = [&] {
constexpr auto expected_result = 100_i;
steps.given("I have an app") = [&] {
auto run_result = 0;
auto [sut_, mocks] = make<app>();
auto& sut = sut_;
fakeit::When(Method(mocks.mock<iapi>(), call)).Return(int(expected_result));
steps.when("I call run on the app") = [&] {
run_result = sut.run();
};
steps.then("I should get an expected result") = [&] {
expect(expected_result == run_result);
};
};
};
};
};
"app"_test = steps | R"(
Feature: Automatic Mocks Injection
Scenario: Dependency Injection
Given I have an app
When I call run on the app
Then I should get an expected result
)";
}
Solutions
struct mocks_provider : boost::di::config {
struct mocks {
template <class...> using is_creatable = std::true_type;
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, [[maybe_unused]] TArgs&&... args) const
-> boost::di::aux::owner<T*> {
if constexpr (std::is_abstract_v<T>) {
the_mock_ = std::make_shared<fakeit::Mock<T>>();
return std::addressof(mock<T>().get());
} else {
return new T{std::forward<TArgs>(args)...};
}
}
template <class T> auto& mock() const {
return *static_cast<fakeit::Mock<T>*>(the_mock_.get());
}
private:
// using shared_ptr here for the type-erased destructor
static inline std::shared_ptr<void> the_mock_;
};
auto provider(...) const { return mocks{}; }
};
struct mocks_provider : boost::di::config {
struct mocks {
template <class...> using is_creatable = std::true_type;
static inline std::shared_ptr<void> ptr;
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, TArgs&&... args) const
-> boost::di::aux::owner<T*>{
if constexpr ( requires(T*t){ {t->call()}-> std::same_as<int>;} )
{
auto * raw_ptr = new fakeit::Mock<T>;
ptr.reset(raw_ptr);
return reinterpret_cast<T*>(&raw_ptr->get());
} else {
return new T{args...};
}
}
template<class T> auto& mock()
{
return *(reinterpret_cast<fakeit::Mock<T>*>(ptr.get()));
}
};
auto provider(...) const { return mocks{}; }
};
struct mocks_provider : boost::di::config {
struct mocks {
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, TArgs&&... args) const -> boost::di::aux::owner<T*> {
if constexpr (std::is_polymorphic_v<T>) {
auto mock = std::make_shared<fakeit::Mock<T>>();
mock_ = mock;
return std::addressof(mock->get());
} else {
return new T{args...};
}
}
template <class...>
using is_creatable = std::true_type;
template<class T>
auto& mock() {
return *std::static_pointer_cast<fakeit::Mock<T>>(mock_);
}
static inline std::shared_ptr<void> mock_{};
};
auto provider(...) const { return mocks{}; }
};