2

While I do see the benefits of IoC containers (as I've used them a fair bit), I don't see how they can be incorporated within TDD unit tests (note, I'm only interested in unit tests here, not integration tests).

When I TDD, I refactor constructors to use IoC, so that I can inject fake dependencies wherever I might need to. Implementing a container implies that I'd be deviating from the red-green-refactor-repeat loop and adding code that wouldn't be covered by my tests.

Now let's say that you somehow (with great design prowess) managed to hook in a container in your TDD life-cyle. You certainly aren't meant to create instances in your unit test by resolving dependencies, as strictly speaking, that turns it into an integration test (bringing in multiple production components).

So my questions are:

1) In what scenario might you need a container while unit testing within TDD?

2) Assuming a valid scenario for (1) exists, how would you go about incorporating a container without breaking away from red-green-refactor-repeat?

I should clarify that by 'need', I'm talking about a stage you'd get to where manually managing DI gets tedious because you have a massive object graph.

IMPORTANT: I'm not asking about containers for your test. I'm asking strictly about production containers. I have a feeling that an IoC container cannot be implemented in a TDD life-cyle without breaking away from red-green-refactor-repeat (rgrr) and managing the container would have to be done in a sort of parallel way, if that makes sense.

  • 2
    Re: your edit. From a TDD perspective, the way you instantiate your objects (i.e. whether you're using an IoC container or not) should make no difference whatsoever, unless the way you're instantiating your objects is what you're unit testing. – Robert Harvey Feb 11 '18 at 22:06
  • 2
    It follows that, if you're not testing the way you instantiating your objects, then you shouldn't use a DI container, since it adds additional complexity for no additional benefit. Testing involving a DI container could arguably be considered integration testing anyway. – Robert Harvey Feb 11 '18 at 22:37
  • @Robert Harvey Yes, it definitely turns it into an integration test, no question about that. I think I didn't make it clear that the tests under TDD I'm concerned with are unit tests. I'll edit my question to reflect that. But given that I'm after 'unit' tests, does my statement about the construction/item-registration of a production container having to be done parallel to RGRR make more sense? – Ash Feb 12 '18 at 6:17
  • I'm of the mind that IoC containers are absolutely irrelevant to TDD, unless it's the container and its behavior that you are testing. The manner in which classes are instantiated has no effect on their behavior under test. Consequently, what you do with your container while you red-green-refactor is irrelevant. – Robert Harvey Feb 12 '18 at 16:25
  • @Robert Harvey That's more or less what I feel as well. If you want to turn this into an answer, I can accept it. One thing that I would like you to describe in the answer is how an irrelevant procedure can co-exist with strict RGRR, i.e, how does one step outside this cycle to construct/manage the container. – Ash Feb 12 '18 at 22:32
6

I can do without IoC containers, TDD or not.

I can work with IoC containers, TDD or not.

I can make a grand mess of things with IoC containers, TDD or not.

IoC containers come in a great many flavors and are sold a great many ways. After mastering them and mastering doing without them I've only identified one thing they do that is hard to do on your own. They move construction into a different language.

Rather then just construct your graph of persistent objects in main they have you compose the object graph in some different language like xml or json. This drives home the idea of separating construction code from behavior code. This is a good thing but if you're disciplined enough to do that without training wheels the IoC container isn't getting you much.

But, if you are going to use IoC containers, for whatever reason. then first be sure you're not spreading it all over the place. A IoC container that shows up once in main is no more an anti pattern then an abstract factory. If every darn object knows about it directly, well you've created a Service Locator. No really, I don't care how popular your IoC container is or how much you paid for it, if you use it that way then you're making a mess. Keep it in one tidy little place where nothing else knows about it and what you have is a factory that pops out object graphs as defined in that screwy xml. This is not the anti pattern. This is fine.

If you want to use TDD while using a IoC container used this way you're fine because the object you're testing doesn't even know the IoC container exists.

Maybe you have some obsessive need to test whatever the IoC container exists in. If you're doing it right then that shouldn't be very many things. But fine. If you must test this too then remember that the IoC container is itself a dependency that can be injected and stubbed. My gut says if you need to test this then you're doing something weird but even if you are then you can still do TDD with a container. Unless you're not just doing something weird but something very wrong.

Remember, when you test something it's your job to give it what it needs in a reliable way so that if other stuff breaks, the test still passes. This test should only fail when the unit under test fails. Bear in mind that a "unit" might be one object or an entire object graph. Stay true to whatever boundary you set for your test. Make the boundary clear so when the test fails people know where to look.

If that boundary must include some dopy little IoC container then so be it. If it doesn't, but you are stuck depending on it, at least give the test its own independent container.

But that's just a pain in the ass. I much prefer it when most of my objects don't even know a IoC container exists. When they'd don't I can test them without one just fine. What they don't know can't hurt them.

I should clarify that by 'need', I'm talking about a stage you'd get to where manually managing DI gets tedious because you have a massive object graph.

A massive object graph will be tedious to deal with manual or not. If your unit under test really cares about the whole graph and you just don't feel happy making the whole graph by hand without the IoC container then fine. Use one to build what the test needs. But the test gets its own independent instance of the IoC and it's config info (unless that right there is what you're testing). That isolates the test and makes it independant.

IMPORTANT: I'm not asking about containers for your test. I'm asking strictly about production containers. I have a feeling that an IoC container cannot be implemented in a TDD life-cyle without breaking away from red-green-refactor-repeat (rgrr) and managing the container would have to be done in a sort of parallel way, if that makes sense.

If A depends on B and T want's to test A then T needs some Bish thing to build A with. T could get the Bish thing from a IoC container C or it could just make the Bish thing on it's own. Either way the red green refactor cycle is fine.

Neither A nor B even know about C so T can use whatever C thing it likes or no C thing at all to get it's A and B. If you can't say things like this you aren't doing the Inversion of Control part of your IoC container right at all.

The classic red green refactor cycle: You write T before you write A. You're red. You write A and feed it some Bish thing (that should already be under some other test) using a C made just for this T. You're green. you refactor and A changes. T is still green.

Refactoring Against The Red Bar gives us formal steps for refactoring a working test:

You want to refactor T because you decide you don't need C or want a different C. So you change T. Now it's green but untrusted because we've only seen it pass. We've never seen it fail. We've never tested the test. We messed up. Let's go back to the end of the classic red green refactor cycle above where we have A under test T which has been seen to both pass and fail. What we should have done, to refactor how T uses C, was this:

  • Run the test T
    • Note the green bar
    • Break the code in A
  • Run the test T
    • Note the red bar
    • Refactor T to change how it uses C
  • Run the test T
    • Note the red bar
    • Un-break the code in A
  • Run the test T
    • Note the green bar

That's refactoring against the red bar. It lets you refactor tests just as formally as you refactor code under test. I first talked about it here. Using that you should be able to force your IoC container to dance to the tune of TDD just fine.

  • 1
    To be fair, it doesn't have to be in a different language. Most of the decent containers let you register interfaces against the class implementations you want to use, and as long as your constructors take interfaces as parameters, it all just automagically works. Some containers allow you to decorate your classes with marker attributes to show the container your desired mappings. Neither of those techniques requires you to leave your programming language. – Robert Harvey Feb 11 '18 at 3:20
  • 1
    In any case, I'm not sure that I see any value in using DI in Unit Tests, unless you're trying to test the container itself. – Robert Harvey Feb 11 '18 at 3:27
  • 3
    Any source file that I can look at and tell what framework you're using is as dependant on that framework as it is on the language. If C# has some magical way to decouple from that problem I haven't seen it. Standardized annotations help but still often represent my classes knowing more than I want them to know. – candied_orange Feb 11 '18 at 3:28
  • 1
    Well, you don't need to decorate classes with attributes; that's just a convenience. You can still register your types in most containers using plain old C#. So I don't see escaping out of my programming language as the principal benefit; I see the principal benefit of DI as allowing me to stand up an object and all of its dependencies and all of those dependencies dependencies et al without having any knowledge of the registrations. – Robert Harvey Feb 11 '18 at 3:29
  • 2
    @RobertHarvey Ah, so that's written in C#. That's perfectly fine as long as that line is not in either of those source files. I'll take that over a bunch of attributes/annotations scattered everywhere any day. – candied_orange Feb 11 '18 at 3:38
2

@CandiedOrange answered the Question as to why it's a good idea to use explicit registration and comments provide more detail. I won't repeat that, since I wholeheartedly agree.

If you follow this advice, and do explicit registration in code, testing it is trivial. Even in TDD fashion (which I like a lot), very simple tests and equally simple implementation. Thus my answer is:

  1. Not necessarily. I do not think testing this is very useful in a UnitTest, I think of it as an orthogonal issue. More like a config file. Some integration tests might be useful.
  2. If you think it helps to have unit tests for this, going about it seams simple, if you configure it with code in your main function (as suggested by CandiedOrange). In this case, it's simply refactoring. Replacing manual wiring up with registration and startup code, while keeping your tests green.
  • I do not dispute explicit registration, since this is how I've used containers as well. I'm also an advocate of doing all registration in one place. But regardless, you are stepping outside the RGRR bounds because container construction and registration has nothing to do with what you are testing. If you're unit testing a Person class let's say, you are most likely testing its behaviour, including how it interacts with its dependencies. You are certainly not testing how it's constructed. Please edit your answer with a counter example if you think I'm mistaken. – Ash Feb 11 '18 at 10:22
  • Also, just to clarify what you mean by "testing this" and "unit tests for this"...are you talking about unit tests for the container itself? Because that's definitely not what I'm asking here. – Ash Feb 11 '18 at 10:35
1

The IoC container becomes useful when part of your software has to instantiate various classes based on runtime information.

For example MVC web controllers must be instantiated when a request comes in which matches the route for the controller.

So your first red test which pushes you down the container road is : 'When a request has path X, Controller Y is created with all its required services'

You might start off writing a hard coded controller factory to make the test pass, but as you develop more and more controllers with different requirements at some stage you will want to refactor into a more generic factory which will resemble a container.

Given that you know such things as containers and generic factories exist prior to starting your code. It's not unreasonable to skip those steps and jump straight into IoC containers.

Proponents of TDD would argue that its very design pushes you towards 'good' code with these advanced structures.

I have a feeling though, that if you are very strict about writing the least amount possible to make a test pass, the heat death of the universe might occur before things like object orientated code and IoC containers 'evolve' from TDD.

Just assume they will eventually and skip those steps.

  • Which steps are you referring to when you say "skip"? – Ash Feb 11 '18 at 21:06
  • iterations of TDD write test, write code, refactor – Ewan Feb 12 '18 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.