8

This question has bothered me for a few days, and it feels like several practices contradict each other.

Example

Iteration 1

public class FooDao : IFooDao
{
    private IFooConnection fooConnection;
    private IBarConnection barConnection;

    public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
    {
        this.fooConnection = fooConnection;
        this.barConnection = barConnection;
    }

    public Foo GetFoo(int id)
    {
        Foo foo = fooConection.get(id);
        Bar bar = barConnection.get(foo);
        foo.bar = bar;
        return foo;
    }
}

Now, when testing this, I would fake IFooConnection and IBarConnection, and use Dependency Injection (DI) when instantiating FooDao.

I can change the implementation, without changing the functionality.

Iteration 2

public class FooDao : IFooDao
{
    private IFooBuilder fooBuilder;

    public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
    {
        this.fooBuilder = new FooBuilder(fooConnection, barConnection);
    }

    public Foo GetFoo(int id)
    {
        return fooBuilder.Build(id);
    }
}

Now, I won't write this builder, but imagine it does the same thing FooDao did before. This is just a refactoring, so obviously, this doesn't change the functionality, and so, my test still passes.

IFooBuilder is Internal, since it only exists to do work for the library, i.e it isn't a part of the API.

The only problem is, I no longer comply to Dependency Inversion. If I rewrote this to fix that problem, it might look like this.

Iteration 3

public class FooDao : IFooDao
{
    private IFooBuilder fooBuilder;

    public FooDao(IFooBuilder fooBuilder)
    {
        this.fooBuilder = fooBuilder;
    }

    public Foo GetFoo(int id)
    {
        return fooBuilder.Build(id);
    }
}

This should do it. I have changed the constructor, so my test needs to change to support this (or the other way around), this isn't my issue though.

For this to work with DI, FooBuilder and IFooBuilder need to be public. This means that I should write a test for FooBuilder, since it suddenly became part of my library's API. My problem is, clients of the library should only be using it through my intended API design, IFooDao, and my tests acts as clients. If I don't follow Dependency Inversion my tests and API are more clean.

In other words, all I care about as the client, or as the test, is to get the correct Foo, not how it is built.

Solutions

  • Should I simply not care, write the tests for FooBuilder even though it is only public to please DI? - Supports iteration 3

  • Should I realise that expanding the API is a downside of Dependency Inversion, and be very clear about why I chose not to comply to it here? - Supports iteration 2

  • Do I put too much emphasis on having a clean API? - Supports iteration 3

EDIT: I want to make it clear, that my problem is not "How to test internals?", rather it is something like "Can I keep it internal, and still comply to DIP, and should I?".

Chris Wohlert
  • 529
  • 3
  • 15
  • I can edit my question to add the tests if you want. – Chris Wohlert May 31 '16 at 09:23
  • 1
    "FooBuilder and IFooBuilder need to be public". Surely only `IFooBuilder` needs to be public? `FooBuilder` need only be exposed to the DI system via some sort of "get default implementation" method that returns an `IFooBuilder` and so can remain internal. – David Arno May 31 '16 at 09:52
  • I guess that could be a solution. – Chris Wohlert May 31 '16 at 10:30
  • 2
    This has been asked here on Programmers at least a dozen times, using different terms: "I want to make a difference between `public` for library APIs and `public` for testing". Or in the other form "I want to keep methods private to avoid appearing them in the API, how do I test those private methods?". The answers are always "live with the broader public API", "live with tests through the public API", or "make methods `internal` and make them visible to the test code". – Doc Brown May 31 '16 at 17:34
  • I would probably not replace the ctor, but introduce a new one and chain them together. – RubberDuck Jun 01 '16 at 00:00
  • @DocBrown That is not at all what I'm trying to do. I am not asking how I make it possible to test the builder. As stated, the builder provides no new functionality, so I should not need to test it, however, DIP makes it part of the API, which forces me to test it. – Chris Wohlert Jun 01 '16 at 07:02
  • @DocBrown It might grease the wheels if you could post links to some of these Qs if you can lay hands on them... – Robbie Dee Jun 01 '16 at 08:49
  • @RobbieDee, It really wouldn't, please see my edit to the question. – Chris Wohlert Jun 01 '16 at 09:13
  • @ChrisWohlert Whatever. The answers are still the same... – Robbie Dee Jun 01 '16 at 09:26
  • @RobbieDee, I really don't think it is. You might, at the time you wrote this comment, have misunderstood my intent. This is my bad though, everyone seems to misunderstand what I'm trying to do. – Chris Wohlert Jun 01 '16 at 10:28
  • Are there some extra members you are eliding from IFooDao? because otherwise you are just repeating IFooBuilder. And both interfaces could be replaced with Func – Caleth Jun 01 '16 at 10:44
  • Hi Caleth, Yeah, something like Create, Delete and Update. More importantly, that would delegate how to build a Foo to the client, which is the part I don't want it to care about. Interesting take on it though. – Chris Wohlert Jun 01 '16 at 11:40

4 Answers4

5

I'm going to share some experiences/observations of various code bases I've seen. N.B. I'm not advocating any approach in particular, just sharing with you where I see such code is going:

Should I simply not care, write the tests for FooBuilder even though it is only public to please DI

Before DI and automated testing, the accepted wisdom was that something should be restricted to the scope required. Nowadays however, it isn't uncommon to see methods that could be left internal made public for ease of testing (since this usually happens in another assembly). N.B. there is a directive to expose internal methods but this more or less amounts to the same thing.

Should I realise that expanding the API is a downside of Dependency Inversion, and be very clear about why I chose not to comply to it here?

DI does undoubtedly incur an overhead with additional code.

Do I put too much emphasis on having a clean API

Since DI frameworks do introduce additional code, I've noticed a shift towards code that while written in the DI style does not use DI per se i.e. the D from SOLID.

In summary:

  1. Access modifiers are sometimes loosened to simplify testing

  2. DI does introduce additional code

In the light of 1 & 2, some developers are of the view that this is too much of a sacrifice and so simply elect to depend on abstractions initially with the option to retro-fit DI later.

Robbie Dee
  • 9,717
  • 2
  • 23
  • 53
  • Thanks for your answer. I just want to note, that loosening modifiers (make them public) complicates my testing, not simplifies it. – Chris Wohlert May 31 '16 at 09:23
  • 4
    It does indeed which is why some developers opt for the [InternalsVisibleTo](https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute(v=vs.110).aspx) attribute for internal methods rather than expose them as public. – Robbie Dee May 31 '16 at 09:25
  • And make internals visible to my Composition Root or the tests? – Chris Wohlert May 31 '16 at 09:26
  • I've only ever seen it used to expose the internal methods of a class to the test assembly. I think if it was used beyond this, it would muddy the waters somewhat. – Robbie Dee May 31 '16 at 09:33
  • 1
    "Nowadays however, it isn't uncommon to see methods that could be left internal made public for ease of testing (since this usually happens in another assembly). " - really ? – Brian Agnew May 31 '16 at 14:01
  • @BrianAgnew N.B. the Implement Interface context menu creates public methods from the interface *by default* although you could of course implement the interface explicitly and do as you wish. – Robbie Dee May 31 '16 at 14:35
  • @RobbieDee: Just realized why having `InternalsVisibleTo` in a Unit Testing/Interfaces/DI/Mocking world is so important. – Robert Harvey May 31 '16 at 18:17
  • @RobbieDee, I just want to say, that my problem is not that I cannot test it while it is internal. My problem is, that I cannot keep it internal and still comply to DIP, and since it becomes public, I will have to test it. – Chris Wohlert Jun 01 '16 at 07:08
  • Yep - additional code can require additional testing. *What to test* is a rather large topic - some try to test everything, some test the critical path, some have arbitrary code coverage requirements. You pays your money, you takes your choice... – Robbie Dee Jun 01 '16 at 08:52
  • @RobbieDee, There is no additional code. What I'm saying is, if I don't comply to DIP, I don't need (and shouldn't need) the test, but if I comply to DIP (without there being more code) I will need the test. – Chris Wohlert Jun 01 '16 at 09:00
  • 3
    @BrianAgnew alas yes. Its an anti-pattern along with "we must have 100% unit test coverage", including getters and setters :-( Why are coders so unthinking nowadays?! – gbjbaanb Jun 01 '16 at 09:03
  • Please note that I am not making anything public so that I can test it, I am making it public to comply to DIP and use DI. The result of doing this, is an expanded API, which in turn requires me to write additional tests (tests I wouldn't need to write if I didn't use DI). – Chris Wohlert Jun 01 '16 at 09:09
  • 2
    @ChrisWohlert simple solution. Don't replace the ctor. Add a new one. Chain them together. Leave your existing test in place. It implicitly tests your builder and your code is still covered. Only write tests if its valuable to do so. There is a point where you get diminishing returns on your investment. – RubberDuck Jun 01 '16 at 09:18
  • @RubberDuck, Are you thinking something like `public FooDao(IFooConnection fooConnection, IBarConnection barConnection) : this(new FooBuilder(fooConnection, barConnection))`? If so, this will not comply to DIP, and I might as well stick with example no. 2. – Chris Wohlert Jun 01 '16 at 09:22
  • 1
    @ChrisWohlert there is *absolutely nothing* wrong with providing a convenience constructor. You're still injecting the class' *true* dependencies. How do you think DI was done before all those "fancy" IoC container frameworks came around? – RubberDuck Jun 01 '16 at 09:25
  • @RubberDuck, My problem here is DIP, not DI / IOC, I am dependant upon an implementation, not an abstraction. – Chris Wohlert Jun 01 '16 at 09:26
  • 1
    Then abstract it @ChrisWohlert. I fail to see the problem here. I think you may have a case of analysis paralysis. Be pragmatic. Write the code that works well enough and move on. – RubberDuck Jun 01 '16 at 09:30
  • 1
    I believe @RubberDuck is right. Look - forget testing and DI for the moment. DIP is nothing more than relying on the abstractions rather than concretions to allow concretions to be easily replaced. In light of this your statement *The only problem is, I no longer comply to Dependency Inversion* to my mind appears to be patently untrue. You're refactoring for the sake of it. I'd say go with iteration 2. – Robbie Dee Jun 01 '16 at 09:48
  • @RubberDuck I don't see how that differs from iteration 3, other than syntax. – Chris Wohlert Jun 01 '16 at 10:09
  • @RobbieDee, I prefer iteration 2 as well. And you make a very good point, I refactor for the sake of it, which is why it bothers me so much that, in iteration 3, I would need to write a test for FooBuilder. – Chris Wohlert Jun 01 '16 at 10:10
  • 1
    "I refactor for the sake of it"... Well there's your mistake. – RubberDuck Jun 01 '16 at 10:13
  • @RubberDuck, I refactor because of SRP, that's my sake, but from the clients' POV, nothing changed. – Chris Wohlert Jun 01 '16 at 10:15
3

I'd go with:

Should I realise that expanding the API is a downside of Dependency Inversion, and be very clear about why I chose not to comply to it here? - Supports iteration 2

However, in The S.O.L.I.D. Principles of OO and Agile Design (a at 1:08:55) Uncle Bob says that his rule about dependency injection is don't inject everything, you inject only at strategic locations. (He also mentions that the topics of dependency inversion and dependency injection are the same).

That is, dependency inversion is not meant to be applied on all class dependencies in a program. You should do it at strategic locations (when it pays of (or might pay of)).

0

I don't particularly buy this notion that you have to expose private methods (by whatever means) in order to perform suitable testing. I would also suggest that your client facing API is not the same as your object's public method e.g. here's your public interface:

interface IFooDao {
   public Foo GetFoo(int id);
}

and there's no mention of your FooBuilder

In your original question, I would take the third route, using your FooBuilder. I would perhaps test your FooDao using a mock, thus concentrating on your FooDao functionality (you can always inject a real builder if it's easier) and I would have separate tests around your FooBuilder.

(I'm surprised mocking hasn't been referred to in this question/answer - it goes hand-in-hand with testing DI/IoC-patterned solutions)

Re. your comment:

As stated, the builder provides no new functionality, so I should not need to test it, however, DIP makes it part of the API, which forces me to test it.

I don't think this widens the API you present to your clients. You can restrict the API your clients use via interfaces (if you so wish). I would also suggest that your tests shouldn't just surround your public-facing components. They should embrace all the classes (implementations) that support that API underneath. e.g. here's the REST API for the system I'm currently working on:

(in pseudo-code)

void doSomeCalculation(json : CalculationRequestObject);

I have one API method, and 1,000's of tests - the vast majority of which are around the objects supporting this.

Brian Agnew
  • 4,676
  • 24
  • 19
  • *I don't particularly buy this notion that you have to expose private methods (by whatever means) in order to perform suitable testing* - I don't believe anyone is advocating this... – Robbie Dee Jun 01 '16 at 10:04
  • But you said in the above - 'Access modifiers are sometimes loosened to simplify testing' ? – Brian Agnew Jun 01 '16 at 10:05
  • Internals *can be* exposed to the test assembly and the implicit interface implementation creates public methods. I was never advocating exposing private methods... – Robbie Dee Jun 01 '16 at 10:08
  • From my PoV, 'Internals can be exposed to the test assembly' amounts to the same thing. I'm not convinced that testing internals (whether marked as private or otherwise) is a worthwhile exercise, unless as a regression test whilst refactoring poorly structured code – Brian Agnew Jun 01 '16 at 10:10
  • Hi Brian, I would, like you, fake the IFooBuilder when parsed to FooDao, but doesn't this mean I would need another test for the implementation of IFooBuilder? We are finally approaching the real issue, and not whether or not I should expose internals. – Chris Wohlert Jun 01 '16 at 10:13
  • Yes you *would* require another test. I don't think that's a bad thing. I understand that you now have a test for a class which *supports* your DI framework, but that class is used in production code, and is supporting your testable solution. As such it's a valid candidate for testing and I don't worry about such things. My main concern is coverage of application code paths via tests and whatever technique supports that best is, to my mind, a suitable implementation technique – Brian Agnew Jun 01 '16 at 10:18
  • Lol - it is patently *not* the same thing. It just allows the test code to be in an external assembly rather than in the same assembly as the code under test. – Robbie Dee Jun 01 '16 at 10:19
  • It appears an edit to my answer removed the pertinent line that these were my *observations* of such code - not endorsements. I've restored this now. – Robbie Dee Jun 01 '16 at 10:25
  • @BrianAgnew, In iteration 2, I was testing FooBuilder via FooDao, i.e I was testing internal implementation through my library's public API. This is exactly what I want. How a Foo is built should not be a concern for my test, only that is is being built. However, a few more good thoughts like that and I might change what it is I want. :) – Chris Wohlert Jun 01 '16 at 10:56
  • 'How a Foo is built should not be a concern for my test, only that is is being built' - that's one use of mocks – Brian Agnew Jun 01 '16 at 10:57
  • Yes, it feels like FooDaoTest.GetFoo becomes obsolete. FooBuilderTest.Build with fake connections tests that a correct foo is returned given these connections. FooDaoTest.GetFoo just tests that it returns the object the fake builder provides. Is this a problem, if not, I might have the answer as was looking for, and yet, it just seems odd that this refactoring messes up a perfectly good test. – Chris Wohlert Jun 01 '16 at 11:03
  • Fundamentally, I wouldn't bother testing methods that are provided to give me internal info. What's the point? You're testing stuff (methods/accessors) that aren't sued in production. I would write my tests from the PoV of your desired public API, and use mocks where appropriate to assert interactions, capture arguments etc. – Brian Agnew Jun 01 '16 at 11:17
  • I agree for the most part, but if my FooBuilder has an error, my FooDaoTest would still pass, and I would be none the wiser. And FooBuilder is in production, just not called by the client. – Chris Wohlert Jun 01 '16 at 11:37
  • That's why you have a test around FooBuilder. You now know that your FooDao works, but your FooBuilder doesn't – Brian Agnew Jun 01 '16 at 11:52
  • But you just said, to test from the PoV of the desired public API, but the FooBuilder is not a part of my desired public API. It is part of my undesired public API, i.e my client doesn't care about how a Foo is made. – Chris Wohlert Jun 01 '16 at 12:19
0

Why do you want to follow the DI in this case if not for testing purposes? If not only your public API, but also your tests are really cleaner without a IFooBuilder parameter (as you wrote), it does not make sense to introduce such a class. DI is not an end in itself, it should help you to make your code more testable. If you do not want to write automated tests for that particular level of abstraction, don't apply DI at that level.

However, lets assume for a moment you want to apply DI for allowing to create unit tests for the FooDao constructor in isolation without the building process, but still do not want a FooDao(IFooBuilder fooBuilder) constructor in your public API, only a constructor FooDao(IFooConnection fooConnection, IBarConnection barConnection). Then provide both, the former as "internal", the latter as "public":

public class FooDao : IFooDao
{
    private IFooBuilder fooBuilder;

    // only for testing purposes!    
    internal FooDao(IFooBuilder fooBuilder)
    {
        this.fooBuilder = fooBuilder;
    }

    // the public API
    public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
    {
        this.fooBuilder = new FooBuilder(fooConnection, barConnection);
    }    

    public Foo GetFoo(int id)
    {
        return fooBuilder.Build(id);
    }
}

Now you can keep FooBuilder and IFooBuilder internal, and apply the InternalsVisibleTo to make them accessible by unit tests.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • The first part of your question is exactly what I am looking for. An answer for the question: "Should I use DIP here". I don't want that second constructor though. If I don't comply to DIP, I don't need to make IFooBuilder public, and I can stop faking / testing it. In short, you're telling me to stick to iteration 2. – Chris Wohlert Jun 01 '16 at 10:49
  • Honestly, if you remove everything from "However", I will accept the answer. I think the first part explains very well when and why I should / shouldn't use DI. – Chris Wohlert Jun 01 '16 at 11:55
  • I'd just add the caveat that DI isn't *just* about testing - it merely simplifies swapping one concretion with another. As to whether you need this here is something only you can answer. As [17 of 26](http://programmers.stackexchange.com/a/59822/73508) wonderfully puts it - somewhere between YAGNI & PYIAC. – Robbie Dee Jun 01 '16 at 12:32
  • @ChrisWohlert: I am not going to remove the 2nd part just because you don't like to apply it for your situation. You do not need tests on that level - fine, apply part 1, don't use DI here. You want tests **and** avoid to have certain parts in the public API - fine, you need two entry points in your code with different access modifiers, so apply part 2. Everone can pick the solution which suits him best. – Doc Brown Jun 02 '16 at 05:04
  • Sure everyone can pick the solution they want, but you misunderstood the question if you think I wanted to test the builder. That's what I tried to get rid off. – Chris Wohlert Jun 02 '16 at 05:09
  • Your example exposes the parts I don't want it to, as well as not complying to DIP. It is not just a personal preference that I don't want it. – Chris Wohlert Jun 02 '16 at 05:17