4

Code from Spring in Action :

public class DamselRescuingKnight implements Knight {
    private RescueDamselQuest quest;
    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }
    public void embarkOnQuest() {
        quest.embark();
    }
}

public class BraveKnight implements Knight {
    private Quest quest;
    public BraveKnight(Quest quest) {
        this.quest = quest;
    }
    public void embarkOnQuest() {
        quest.embark();
    }
}


public class BraveKnightTest {
    @Test
    public void knightShouldEmbarkOnQuest() {
        Quest mockQuest = mock(Quest.class);
        BraveKnight knight = new BraveKnight(mockQuest);
        knight.embarkOnQuest();
        verify(mockQuest, times(1)).embark();
    }
}

I understand the use of dependency injection, which allows us to switch implementation without modifying the depending code.

The book says "terribly difficult to write a unit test ...".

However, I am not able to understand how it will be very difficult for unit-testing without dependency injection! My intuition refuses to co-operate !

Can you start writing junit/unit testing for the class "DamselRescuingKnight" and for any other better example class (without DI), to make me realize the point/stage at which DI makes unit testing easier ?

1
  • 2
    Your code is very simple so it is hard to really explain. If quest.embark is a complicated amount of code, going to a database, involving user input, and multiple computers, then you would want to replace the complicated code with something simpler. But, in your code it is pointless to even unit test. quest.embark needs to be tested, but the code you have here doesn't. Commented Mar 20, 2016 at 21:37

3 Answers 3

6

The difficulty in your above example comes when you try to test DamselRescuingKnight. Assume, you want to test that one (see below)

public class DamselRescuingKnight implements Knight {
    private RescueDamselQuest quest;
    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }
    public void embarkOnQuest() {
        quest.embark();
    }
}


public class DamselRescuingKnightTest {
    @Test
    public void knightShouldEmbarkOnQuest() {
        DamselRescuingKnight knight = new DamselRescuingKnight ();
        knight.embarkOnQuest();
        // now what?            
    }
}

how can you be sure that knight.embarkOnQuest() does actually do anything? The answer is that you can't because you can't access the quest instance it uses internally.

Now in order to be able to test such a class, you would add a getQuest() method to the Knight, and then also add a isEmbarked() method to Quest. It is also quite fair to say, that this example is very simple, because the knight only calls the quest without parameters, and nothing else. If knight would interact with a quest and also get some weaponary from a Blacksmith, then you would also somehow need to allow access for that. You could probably do all the boilerplate to get that done. But then assume, you're passing parameters to blacksmith - how do you ensure that the passed parameters were correct? Or how do you ensure that the knight gets his/her weapon before going to the quest?

This is where dependency injection comes to the rescue. you can just create mocks (either by using a mock framework, or by implementing your own mocks) so that you can verify that your knight does the expected things.

Sign up to request clarification or add additional context in comments.

2 Comments

I could argue that we shouldn't be testing implementation details. The fact that DamselRescuingKnightTest uses a Quest object internally is an implementation detail. Instead we should only test public API. So presumably DamselRescuingKnightTest has some other methods like getQuestResult() and we would test those.
@Andrey This makes sense but how can we then test things that call out to e.g. databases or external APIs? Say we have a service that calls one of two different APIs depending on some internal logic, how can we test that without a mock?
2

The problem is of course the quest variable. You want to somehow check that the embark() method is invoked. Without being able to replace it with a mocked instance, this is very hard.

If the variable were protected instead of private, the test case could overwrite it by virtue of living in the same package.

You can also use Aspect-Oriented Programming to replace the variable.

But the easiest is if the code is written with dependency injection from the get-go.

You ask to see how AOP can be used. The following is an example of an AspectJ pointcut that you can use in a unit test to replace the RescueDamselQuest instance with a mocked one called MockRescueDamselQuest (apologies if I don't get the syntax exactly right, it has been a while since I used AspectJ):

aspect MockRescueDamselQuestInstantiations {
    RescueDamselQuest around (): call(RescueDamselQuest.new()) {
        return new MockRescueDamselQuest();
    }
}

This will catch any instantiations of RescueDamselQuest (i.e. calls to new RescueDamselQuest()) and return a MockRescueDamselQuest object instead.

Given how much more wiring this requirest, I'd strongly suggest using dependency injection instead!

1 Comment

The only reasonable thing to test is to check if quest.embark() is invoked when embarkOnQuest() is invoked,in this scenario as pointed by @James. You say it is very hard without mocked instance. Can you please try doing and show it is hard? It will give me a perspective. If I somehow deliberately avoid DI,you say it can be done by AOP. Can you please point to one such example?
2

This perplexed me as well when I was reading this in Spring in Action. After reading above answers I wanted to add that when DI is not used then Junit method need to call method of object which is private (which is in accessible) and this object Quest is created in constructor of DamselRescuingKnight so test case for embarkQuest() can't be written. On contrary when using DI then you are externalizing object creation and Junit method can create that object so it will be accessible to it then can test emabarkQuest() which eventually need to test quest method

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.