#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2023 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_SUPPORT_SOURCE 1 #include "Nuclex/Support/Services/LazyServiceInjector.h" #include namespace { // ------------------------------------------------------------------------------------------- // /// Example service providing a few simple math methods class CalculatorService { /// Frees all resources owned by a calculator instance public: virtual ~CalculatorService() = default; /// Calculates the sum of two integers /// First integer that will be part of the sum /// Second integer that will be part of the sum /// The sum of the two integers public: virtual int Add(int first, int second) = 0; /// Multiplies two integers with each other /// First integer that will be multiplied with the other /// Second integer that will be multiplied with the other /// The sum of the two integers public: virtual int Multiply(int first, int second) = 0; }; // ------------------------------------------------------------------------------------------- // /// Example implementation of the calculator service class BrokenCalculator : public virtual CalculatorService { /// Factory method that creates an instance of the broken calculator /// The new broken calculator instance public: static std::shared_ptr CreateInstance( const Nuclex::Support::Services::ServiceProvider & ) { return std::make_shared(); } /// Calculates the sum of two integers /// First integer that will be part of the sum /// Second integer that will be part of the sum /// The sum of the two integers public: int Add(int first, int second) override { return first + second + 1; } /// Multiplies two integers with each other /// First integer that will be multiplied with the other /// Second integer that will be multiplied with the other /// The sum of the two integers public: int Multiply(int first, int second) override { return first + first * second; }; }; // ------------------------------------------------------------------------------------------- // /// Example class that consumes the calculator service class CalculatorUser { /// Initializes the calculator user example /// Calculator service the example will be working with public: CalculatorUser(const std::shared_ptr &calculator) : calculator(calculator) {} /// Performs a calculation using the calculator service /// The result of the calculation public: int CalculateSomething() { return this->calculator->Add(1, 2) + this->calculator->Multiply(2, 2); } /// Calculator service the example has been provided with private: std::shared_ptr calculator; }; // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Support { namespace Services { // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, StandardIsConstructibleWorks) { constexpr bool withoutArguments = std::is_constructible::value; EXPECT_FALSE(withoutArguments); constexpr bool withWrongArgument = std::is_constructible::value; EXPECT_FALSE(withWrongArgument); constexpr bool withTooManyArguments = std::is_constructible< CalculatorUser, std::shared_ptr, int >::value; EXPECT_FALSE(withTooManyArguments); constexpr bool withMatchingArguments = std::is_constructible< CalculatorUser, std::shared_ptr >::value; EXPECT_TRUE(withMatchingArguments); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanCreateIntegerSequence) { EXPECT_NO_THROW( typename Nuclex::Support::Services::Private::BuildIntegerSequence<4> test; test = test; ); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanDetectSharedPtrs) { constexpr bool isIntASharedPtr = ( Nuclex::Support::Services::Private::IsSharedPtr::value ); EXPECT_FALSE(isIntASharedPtr); constexpr bool isSharedPtrASharedPtr = ( Nuclex::Support::Services::Private::IsSharedPtr>::value ); EXPECT_TRUE(isSharedPtrASharedPtr); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanDetectInjectableTypes) { class Evil : public std::shared_ptr { public: virtual void MakeItAbstract() = 0; }; constexpr bool isIntInjectable = ( Nuclex::Support::Services::Private::IsInjectableArgument::value ); EXPECT_FALSE(isIntInjectable); // It's not a shared_ptr constexpr bool isAbstractSharedPtrInjectable = ( Nuclex::Support::Services::Private::IsInjectableArgument::value ); EXPECT_FALSE(isAbstractSharedPtrInjectable); // It's abstract constexpr bool isSharedPtrIntInjectable = ( Nuclex::Support::Services::Private::IsInjectableArgument>::value ); EXPECT_TRUE(isSharedPtrIntInjectable); // Silly but okay constexpr bool isSharedPtrClassInjectable = ( Nuclex::Support::Services::Private::IsInjectableArgument< std::shared_ptr >::value ); EXPECT_TRUE(isSharedPtrClassInjectable); // Alright! } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanDetectConstructorSignature) { class DefaultConstructible {}; constexpr bool defaultConstructibleIsDetected = !std::is_base_of< Nuclex::Support::Services::Private::InvalidConstructorSignature, Nuclex::Support::Services::Private::DetectConstructorSignature >::value; EXPECT_TRUE(defaultConstructibleIsDetected); class OneArgumentConstructible { public: OneArgumentConstructible(const std::shared_ptr &) {} }; constexpr bool singleArgumentIsDetected = !std::is_base_of< Nuclex::Support::Services::Private::InvalidConstructorSignature, Nuclex::Support::Services::Private::DetectConstructorSignature >::value; EXPECT_TRUE(singleArgumentIsDetected); class TwoArgumentConstructible { public: TwoArgumentConstructible( const std::shared_ptr &, const std::shared_ptr & ) {} }; constexpr bool twoArgumentsAreDetected = !std::is_base_of< Nuclex::Support::Services::Private::InvalidConstructorSignature, Nuclex::Support::Services::Private::DetectConstructorSignature >::value; EXPECT_TRUE(twoArgumentsAreDetected); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, RejectsConstructorWithUninjectableArguments) { class UninjectableConstructor { public: UninjectableConstructor(float) {} }; constexpr bool uninjectableConstructorIsRejected = std::is_base_of< Nuclex::Support::Services::Private::InvalidConstructorSignature, Nuclex::Support::Services::Private::DetectConstructorSignature >::value; EXPECT_TRUE(uninjectableConstructorIsRejected); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, RejectsConstructorWithTooManyArguments) { class NineArgumentConstructible { public: NineArgumentConstructible( const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr &, const std::shared_ptr & ) {} }; // This test will obviously break if you increase the argument limit beyond eight constexpr bool nineArgumentsAreAccepted = !std::is_base_of< Nuclex::Support::Services::Private::InvalidConstructorSignature, Nuclex::Support::Services::Private::DetectConstructorSignature >::value; EXPECT_FALSE(nineArgumentsAreAccepted); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanBindServiceToImplementation) { LazyServiceInjector serviceInjector; serviceInjector.Bind().To(); std::shared_ptr service = serviceInjector.Get(); ASSERT_TRUE(!!service); EXPECT_NO_THROW(service->Add(1, 2)); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, ServiceCanSelfImplement) { LazyServiceInjector serviceInjector; serviceInjector.Bind().ToSelf(); std::shared_ptr service = serviceInjector.Get(); ASSERT_TRUE(!!service); EXPECT_NO_THROW(service->Add(1, 2)); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanBindServiceToFactoryMethod) { LazyServiceInjector serviceInjector; // Simple form of .ToFactoryMethod() that expects the factory method to // return the service type serviceInjector.Bind().ToFactoryMethod< &BrokenCalculator::CreateInstance >(); std::shared_ptr service = serviceInjector.Get(); ASSERT_TRUE(!!service); EXPECT_NO_THROW(service->Add(1, 2)); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanBindServiceToFactoryMethodReturningImplementation) { LazyServiceInjector serviceInjector; // More elaborate form of .ToFactoryMethod() where the factory methood // can return any type that inherits from the service type serviceInjector.Bind().ToFactoryMethod< BrokenCalculator, &BrokenCalculator::CreateInstance >(); std::shared_ptr service = serviceInjector.Get(); ASSERT_TRUE(!!service); EXPECT_NO_THROW(service->Add(1, 2)); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanBindServiceToInstance) { LazyServiceInjector serviceInjector; serviceInjector.Bind().ToInstance(std::make_shared()); std::shared_ptr service = serviceInjector.Get(); ASSERT_TRUE(!!service); EXPECT_NO_THROW(service->Add(1, 2)); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanResolveServiceImplementationDependencies) { LazyServiceInjector serviceInjector; serviceInjector.Bind().To(); serviceInjector.Bind().ToSelf(); std::shared_ptr user = serviceInjector.Get(); ASSERT_TRUE(!!user); EXPECT_NO_THROW(user->CalculateSomething()); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, ServiceInstancesAreShared) { LazyServiceInjector serviceInjector; serviceInjector.Bind().ToSelf(); std::shared_ptr first = serviceInjector.Get(); std::shared_ptr second = serviceInjector.Get(); ASSERT_TRUE(!!first); ASSERT_TRUE(!!second); // The service injector should have delivered the same instance both times EXPECT_EQ(first.get(), second.get()); } // ------------------------------------------------------------------------------------------- // TEST(LazyServiceInjectorTest, CanProvideServiceFactoryFunction) { LazyServiceInjector serviceInjector; serviceInjector.Bind().ToSelf(); /* std::shared_ptr (*factory)() = ( serviceInjector.GetServiceFactory() ); */ std::shared_ptr shared = serviceInjector.Get(); ASSERT_TRUE(!!shared); std::shared_ptr first = serviceInjector.Create(); std::shared_ptr second = serviceInjector.Create(); ASSERT_TRUE(!!first); ASSERT_TRUE(!!second); // The service injector should have created a new instance both times EXPECT_NE(first.get(), second.get()); EXPECT_NE(shared.get(), first.get()); EXPECT_NE(shared.get(), second.get()); } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Support::Services