7

I am working on an application whereby I have designed classes to fit into several groups:

  • Immutable: initialised through constructors, uses copy-and-swap idiom (inc. move), can deep copy (i.e. clone), only has "getters" (not "setters") and implements comparison operators (==, !=);
  • Services: stateless classes that have methods that take immutables and/or services to perform desired functions;
  • Builders: factories etc to build immutables.

Unit testing on my immutables has been straightforward. I can use dependency injection through the constructors. This means I can swap in test classes to ensure I am unit testing (as opposed to integration testing). I can use the builders to construct my production objects. This element of my design I am happy with in terms of testing and production.

However, on my services I seem to only be able to keep these classes stateless and unit testable by using dependency injection via the method arguments.

For my services, an example function changes from:

virtual unsigned long foo() const override final;

...to:

virtual unsigned long foo(const ISomeInterface & dependency) const override final;

This means my service classes are testable, but then I have to now instantiate the dependencies outside of the class when using the code in production. For example:

// without dependency injection
Service service;
return service.foo();

// with dependency injection
Service service;
Dependency dependency;
return service.foo(dependency);

This has led to a large number of my service classes now needing at least 1 more argument for each class method. Note - this is the approach I am currently using in my code.

My question is this:

What alternatives do I have to this form of dependency injection that allow me to:

  • unit test stateless classes (without the dependencies)
  • keep these classes stateless
  • reduce / hide code that instantiates the dependencies (particularly if an object has multiple dependencies)

Note - I am also performing integration tests, which tests the real dependencies between objects.

Class Skeleton
  • 231
  • 1
  • 4
  • 10
  • 1
    Why is explicitly passing in dependencies a problem again? – Telastyn May 27 '16 at 13:02
  • @Telastyn - The client calling these stateless classes now needs to know about the dependencies. Secondly, you lose a little readability when you bloat the arguments with dependency types (specifically when you have multiple dependencies). – Class Skeleton May 27 '16 at 13:28
  • 5
    Right, I don't see those as problems. The client _should_ know about dependencies, since they're likely to want to be able to change/specify them. And you _should_ lose a little readability if you have too many dependencies - it's a signal that your function is doing too much. – Telastyn May 27 '16 at 13:42
  • @Telastyn - I agree that the client should know if they are likely to change. However, I am adding the dependency so I can swap out for unit testing. Now, in most cases, the only reason my client code uses the dependency is to fit the unit testing design (i.e. will not be changing the dependencies). – Class Skeleton May 27 '16 at 13:46
  • If it's meaty enough that you'll want to change it for unit testing, it's likely enough that it will need to change in the future. If it's not, then why bother injecting it for testing? – Telastyn May 27 '16 at 13:50
  • @Telastyn - Well, this is more of a case of retro fitting unit tests. However, going forward I would like to create the tests first (i.e. use the unit tests to get the class design right). – Class Skeleton May 27 '16 at 13:52
  • @Telastyn - Also, I am not saying there is a problem and I cannot continue with this approach. I am asking for alternatives. If there is an alternative that can hide dependencies from clients I would like to use it. – Class Skeleton May 27 '16 at 13:53
  • 2
    Do you really need pure stateless classes, or just client-stateless classes *that are configurable*? – Erik Eidt May 27 '16 at 15:07
  • How are your classes stateless if they are constructed containing their dependencies? – Caleth May 28 '16 at 20:07
  • @Caleth - I never said that. Stateless classes are constructed with default constructors. See the examples in my question. – Class Skeleton Jun 02 '16 at 14:12
  • @camelCase Your without DI example implies something like `class Service { Dependency dependency; ... }` Immutable state is still state – Caleth Jun 02 '16 at 14:16
  • +1 Just because I like the organization of your classes. – Mike Jun 17 '16 at 20:03
  • 1
    Consider changing the stateless class methods into namespace scoped functions. – Aluan Haddad Jul 31 '17 at 15:49

4 Answers4

4

If you donot like the additional constructor arguments for the dependencies you need a DI-Container to handle the instance creation for you.

You can use either an existing di-container framework or implement a poor mans version on your own

public PoorMansDiContainer : IPoorMansDiContainer {
    private IService mService = null;
    private IFooService mFooService = null;

    public IService getSingletonService() {
        if (mService == null) {
            mService = new ServiceImpl(getSingletonFooService());
        }
        return mService;
    }
    public IFooService getSingletonFooService() {
        if (mFooService == null) {
            mFooService = new FooServiceImpl();
        }
        return mFooService;
    }
}
k3b
  • 7,488
  • 1
  • 18
  • 31
1

You could configure your stateless classes to refer directly to their dependencies by using templates. As an example:

// a dependency interface...
class ILogger
{
public:
    virtual void logSomething (std::string message);
};

// an interface for a service
class IMyService
{
public:
    virtual std::unique_ptr<MyClass> getMyObject () = 0;
};

template <LogProvider>
class MyServiceImpl : public IMyService
{
public:
     virtual std::unique_ptr<MyClass> getMyObject ()
     {
          return LogProvider::logger->logSomething ("creating my object");
          return std::make_unique<MyClass> ();
     }
};

// at global level
struct GetLogger 
{
    static std::shared_ptr<ILogger> logger;
}

// initialisation code...
GetLogger::logger = // ... make a concrete logger here
std::unique_ptr<IMyServce> myService = 
   std::make_unique<MyServiceImpl<GetLogger>> ();

You can then create instances of MyServiceImpl using any class you wish that contains a logger. This approach creates global data, but as it only uses it indirectly I don't see an issue with it.

Although, that said, I'd personally just abandon the notion of stateless service classes and just switch to immutable service classes instead, as it seams a much simpler solution.

Jules
  • 17,614
  • 2
  • 33
  • 63
0

Use a powerful service locator, which is, as Martin Fowler will tell you, nearly functionally equivalent to DI. Then set the service to whatever mock you like for the duration of the test in the context in which the test is running. e.g. (C# code, sorry)

public class Service()
{
  public void Foo()
  {
    ...
    IDependency dependency = ServiceLocator.Get<IDependency>();
    ...
  }
}

For the test

ServiceLocator.SetForThisThread<IDependency>(typeof(MockDependency));
RunTest();

If anyone would like to complain that you are then creating a dependency on ServiceLocator, I'd really like to understand how that could ever be a problem.

  • 1
    The problem isn't the dependency on ServiceLocator, the problem is the hidden dependency on "the service locator must have an IDependency available for this test". You can't discover this dependency by just looking at Service's interface. (Also, C# code in a question tagged C++ can be confusing if not called out as such.) – Sebastian Redl May 28 '16 at 11:23
  • Sure its useful to have the compiler stop you making a mistake because you didn't set up the hidden dependency, however you will find out you need it with a runtime error the first time you run it. That's a pretty small cost as all the enthusiasts of dynamically typed languages will tell you. It may well be worth paying in this case. – James Ellis-Jones May 28 '16 at 13:24
0

I've managed to clean up my stateless classes by using default arguments. For example,

virtual unsigned long foo(
    const ISomeInterface & dependency = ProductionDependency()) const override final;

I appear to have met all the criteria in my original question...


unit test stateless classes (without the dependencies)

I can unit test by specifying the argument (for example, pass in a fake test object).

// unit test snippet
Service service;
TestDependency dependency;
const auto actual = service.foo(dependency);

keep these classes stateless

The stateless classes still only use the default constructors, and have no data members.


reduce / hide code that instantiates the dependencies (particularly if an object has multiple dependencies)

I needed to make the following changes:

  • The service base class headers now include the production dependency headers
  • The method arguments now include default arguments for each dependency
  • In some cases I needed to change the order of arguments to ensure the default arguments are the final arguments

However, I now no longer need to include these dependencies in my client code. For example,

// client code snippet
Service service;
return service.foo();

This is even the same if my Service class has multiple dependencies. As long as I am using default arguments I can use the same snippet as above. The clients no longer need to know about the dependencies (or more specifically, need to instantiate dependency objects because you get it free via default arguments).

Class Skeleton
  • 231
  • 1
  • 4
  • 10