In a legacy codebase, there are often a considerable amount of dependencies. It makes it hard to introduce unit tests. But since tests are essential to make changes safely, we need techniques to break the dependencies. In this video, I try to explain how to do just that.
Transcription of video
Most legacy code is riddled with dependencies. It is, in my opinion the number one difficulty is trying to create a test harness around code. The main problem was dependencies, so you need to break them before you could introduce tests. By breaking the dependencies requires you to change the code, and those changes are made without the safety net of the tests. In the book “working effectively with legacy code”, Feathers introduced no less than 24 different techniques in how to break dependencies in different contexts. Some of my favorites are ‘Extract Interface’, ‘Adapt Parameter’, and ‘Parameterized Constructor’. I will try to walk you through these three samples on how to use them in this video.
To introduce dependency breaking techniques, we first need an actual implementation that needs to have dependency broken. One example of this is in the dependency breaking Kata, which I have loaded into my ID here. It contains different assignments on, which contains code, which has dependency build-in, which we need to find a way to actually break dependencies, such that we can introduce test. In the first example, the context around that is you have a webshop, and the webshop has a discounting module which you can relate as Marketing campaign, so if we see how it works is that we can, we can apply discounts and depend on whether it is Crazy sale day or how much the customer is purchasing for; it will apply a different amount of discounts. One of the issues with this dependency is that it is un-deterministic so if you are looking for is Crazy sales day, for example, we see that this class discounts only on Fridays and that was actually what just case to watch this method, then depending on whether or not it’s Friday or not Friday. The case will either pass or not pass, and that kind of is not really helpful in running eme-nine unit tests.
So, the first thing we need to do is to find a way to actually break this dependency so that we can inject a mark of the Marketing Campaign from our test method. So the first thing we need to do is to introduce a new constructor that is parameterized so we will do that by introducing a new constructor which has the net marketing campaign as a parameter and the old method will just copy and call this overloaded constructor. Like this, so now I have introduced a parameterized constructor which will allow us to inject a new marketing campaign from our test cases and at the same time we are still preserving the public signature of this class so whoever’s actually still using this constructor which of course will all be assisting code they don’t have to change anything and we still have the possibility to override behavior from our unit test so if we switch to unit tests we can now actually implement test case test testing the discount on Crazy sale date, so we are going to test that ‘discount is applied on crazy sale date.’
So let’s start by creating a mock for the marketing campaign and we will use the moq framework to do that. This will allows us to change the behavior of methods for crazy sale date. It will now return true and create our system in the test and let interns allow us to have the discounted price. Discount for, let us say try to do a discount of 100. We should apply a discount for 15 percent so we will expect to see 85 when we run this test. One thing to notice before we move forward is that the mock framework is only able to actually do mock on either interfaces but since this is an actual implementation the method we are changing behavior on needs to be virtual so we need to switch to the marketing campaign and make it virtual.
So, as long as we are so lucky to have actual code in under our control we can use this method to actually do it, so if we run the test, we will see that now it passes so this is an easy way to kind of introduce test cases into system code by breaking the dependencies and now in this case it was relatively easy to break the dependency in some cases this is much harder. Another great pattern for breaking dependencies is extract interface. So if we have such new situation to construct something same way as the Marketing campaign and our discount class and we already created the parameter as constructor but now we see that the Marketing Campaign actually has a dependency chain It needs a Transaction log, it needs a database connection to actually be able to instantiate.
So, the issue here is that it is increasingly difficult for us to actually implement this dependency chain inside out test case. It might even be that some of these classes like database connection is not even possible to instantiate in the environment we have to run our unit tests in and in this case we can use the extract interface to actually break this dependency chain all the way up at the Marketing Campaign level.
So if we start by doing a refractor to extract the interface, it will create an IMarketing interface for us, so that would be in our Marketing Campaign introduce new interface already put the interface on the marketing campaign and we should be able to use it on our parameterized constructor but of course we need to change that everywhere as this is one of the things that makes it extremely safe to do this refracturing without tests in place because the compiler will let us know of any places where we have mismatch in types. So it’s not anything that’s likely to introduce errors when we do this kind of refactoring.
So now that we have extracted an interface and use that it allows us to create a test case, we can say that the discount’s applied correctly. So, now that the Marketing Campaign is an interface, we would be able to mock it much easier then it was and actual implementation and now we as before we had the issue that we needed to change the Marketing Campaign to have a virtual method that’s no longer needed, since we are using the, , the interface directly. So make the Crazy sale date rich in, true. and for 100 and that’s before we’ll expect the discounts will be applied. 15%, so we’ll have 85 as our discounted price. So we’ll see if that passes and it does and we will notice that we didn’t have to worry about this transaction log or the database connection, because now we have an interface and we can walk the method directly and let the mock framework handle the actual mock implementation that goes on behind the scenes.
So, now we’re building, , on this, , example where we have already introduced the parameterized constructor. So we have the marketing campaign, dependents under control. So now a chance to code a little bit and introduce a discount repository. So instead of actually fetching the or having the the discounts hard coded inside this method, we now have an indirection using a discount repository where we can fetch the actual discounts. So, now this repository is implemented in again and on deterministic way, which makes it difficult to introduce unit tests. So, for example, the Crazy Sale is actually pretty crazy now since now we use a random number as the discount with the maximum value is 15. So, that makes it quite a lot more difficult to actually implement the tests and for the sake of the example, we think this as the discount repository is under the control of some other team. So we can test, , change anything in it. It’s also a school class. So it makes it quite a lot more difficult to actually work with this dependency.
So, if we now want to actually bring this method on the test, we’ll need a way to handle this dependency and since we cannot change the discount repository class, we can use something like extract interface. Our only option is to try to rep the dependency using a technique called a tap parameter. The way it works is that we introduce an adapter which just sits as a proxy between the Cline code, which is this method and the actual implementation. This will allow us to to break the dependency because we are going to introduce an interface that we can mark from the test case and we’re going to have an implementation that’s used for the production code. So to actually do this technique, it’ll start by, extracting and or creating an interface based on the discount repository class. So, start by creating this interface, and if we look at the discount repository, we see that we have an increased sale date. We will put that in and we also see that we have an int. Well, then, with the money limit, put that in as well. Now we have a interface. So, now we need to create our adapter for the interface and we need to remember that this adapter will sit between the clan code and the Jackson implementation. So it essentially just proxy all the calls. So we’ll have it implement the interface and when you constructed to actually inject the dependency. So, we just had that and then we need to proxy all the calls.
Do we need to change the method signature to use the I Discount Repository instead of the implementation? This is the trick that would allow us to actually break the dependency. One thing to notice here is that doing this will force us to change all the clan codes because we cannot preserve the actual signature of the method. So it would go into our test case now. We can create a new fact. Well, we’re going to test that. First of all, that we are hitting this crazy sale day. So we need to, , mock the marketing campaign and we also need to make sure that we are actually calling decreases sale date to get the discount of this day. So I have a bit of set up to do before we can actually call it.
So they can all use the mock framework to do this. First, we need to make sure that we are actually returning the crazy sale date and then we create a mark for the discount repository and we will use the interface for a sense that we actually can mark it and will return 15 instead of a random number, which was the production implementation. So first of all, with check the marketing campaign through the constructor and we’re going to call a discount for. We’re going to discount 100 and we’re going to inject a mock of the repository. So we’ll expect 85 since we’re doing a 15% discount. Then we run the test to see if we have a pass and it’s passing.
A few things to notice here is that, of course, this is only a milestone on actually refactoring the code to become something that’s manageable in the long run. So it’s a technique for breaking these small dependencies such that we can actually introduce tests because it’s test cases that allow us to do more involved refactorings without breaking anything.
Level up your code newsletter.
Feel confident delivering your next project!
Actionable insights on how to improve your code.
Real-life examples on how to apply SOLID, TDD, Design Patterns. Not just hello world examples.
Being a professional developer is not just about code. I touch on many of the other aspects needed to succeed as a developer.