When Unit Testing and Frameworks Collide
I'm a big fan of unit testing. It allows me to be much more confident in the deployability of my code. And that's great. Without going into too much detail ( we can save that for another article ) a unit test is something that tests a small bit of code in isolation from the rest of the system. That small bit of code would, typically, be a class method.
So, what happens when that method relies on the database. Even that is really simple. With code that has been written with testability in mind, you create a mock object that "looks" to the system like the database. You then use that mock object to pretend to be the database and that is what the method is given instead of the real database.
We probably all know that we are talking about mocking and dependency injection and these are more things we should look at in other articles.
So, we have an idea of the utility of unit testing and why we should be a fan. We are also great fans of development frameworks. They save us from the mundane and make development so much faster, easier and for the really geeky amongst us, more fun.
But - what happens when they collide. Let me give you a real world indication of how this can happen using the Laravel/Lumen framework and PHPUnit.
Initially everything is fine. I mock my database models and inject them in my controllers and I can test without the database needing to even be running. That's my perfect world.
Now we use a framework feature. We add useSoftDeletes to a model. Suddenly everything goes wrong. PhpUnit can no longer mock that model. There is code in the framework that is not testable, possibly because of the use of singletons of static class members.
Stack overflow points us at a possible solution. We can introduce a factory to create a class just for testing. Unfortunately this isn't a mock object. It is a full scale database model that requires mysql.
And there is the collision.
Ease of framework against good test practice.
So what do we do? What's the important requirement here? We absolutely need to be able to soft delete, and we absolutely must be able to run a full suite of unit tests without reference to a real database.
I can either sacrifice a requirement or maybe find a different framework that doesn't suffer from this problem. Obviously sacrificing a requirement isn't going to fly. A requirement is... well, required!
If my project is fairly new then I can choose a different framework as I wouldn't have to redevelop too much, but even then I don't really want to. There must be a quicker way.
For me, the answer is simple. I can achieve both requirements by putting a little more effort into application development. Instead of just using the frameworks useSoftDeletes I can create my own soft delete code. I can even use some kind of intermediate class to make sharing those methods easier.
My version of the soft delete would then use testable code. I have sacrificed perhaps a few hours, and yes I have reinvented the wheel. But isn't that better than coupling a database to my unit tests?
I think so. That's what I'm doing. And that's how I'll resolve any further such clashes.