What is BDD testing?
Do you like unit tests? From my observation after a few years in IT and also after working in several IT companies I realized that it is not a thing, which developers like. Unit tests are more like a sad responsibility that we need to do. On one side everybody tells you that you should write unit tests. Every professional does it (and we want to be professional). But on the other side, we prefer to implement new features in the application. They are like “small green fields”. In the end, we can show the client our work. But unit testing? It’s only for us. Nobody sees it. We write unit test once and we will never go back to it. So why do it better? Why don’t we want to treat tests like new features? Why are we okay with code duplication and worse readability? Probably because we are lazy. I think that we should give unit testing a chance because behavior driven development can be nice.
I first heard about the BDD test in one of my previous jobs. And immediately – I loved it because:
- test driven development is very readable, a not-technical person can read them and understand what they do without any problem,
- BDD are also very intuitive, if you don’t know this solution, it’s easy to figure out how they work. Thanks to this feature, the entry threshold is low,
- unit test is very structured, which means that tests written by several developers are similar and that is a big plus if you work with somebody else’s BDD tests.
What is BDD example?
But what is the test driven development? We can say that in BDD tests we are focused more on functionality than on the result. It’s like we look at software development more from the user’s side, then the developer’s. For test cases purposes, I created a very simple service with an interface like below.
It contains CRUD methods and input data validations.
I use the LightBDD library. You can find some similar solutions when it comes to BDD tools. LightBDD is pretty popular, simple, free but first of all, I was convinced to use it by the solution with a partial class (in C#, partial class it’s class of which implementation can exist in several files) proposed by the author. In show solution test is split into two files.
Fixture – include the implementation of the tests. It contains three types of methods:
- given – they set input data,
- when – they run tests methods,
- then – they are assertion checking results of test methods.
Runner – its responsibility is to run the tests. Written methods from a fixture file, we use to build a test like from blocks. It ensures high reusability.
But… Is there any but here? It appears so and I realized that relatively recently. It’s how my tests constructor looked like:
At first glance, everything seems ok. We have here normal dependency mocks and the creation of testing service instantiation. Unfortunately, the problem appears when we have more of these normal mocs and constructor starts looking like this:
Unfortunately, I am the author of this code…
It was an important moment in my journey with BDD tests. I started to wonder about the sense of my implementation. It should improve it and increase readability, but unit test showed that it truly only moved the problem from one place to another. But I didn’t throw this idea away. I still thought that BDD tests are cool and the problem was in me and my implementation. So I was starting to search where I can improve this software development.
How do you write a BBD test?
At first, I thought about mocks. In my constructor, the mocks are the most troublesome part. I thought that the best is to check how other people do it. After a quick research, I found a very interesting article, which you can find here.
The solution extends mock class, in our example (like also in the article) it is Moq. The methods, which we include in the implementation, wrap the mocks setup.
Thanks to this I got a tailor-made bunch of methods. You don’t need to think about what values you need to pass or return and if you need it, you can always add new methods. But first of all, this solution is reusable because the repeating setup is closed in your methods and only you run them. Additionally, thanks to that we return “this” if we need to run several methods from one class, we can do it like with Fluent API.
This solution improves code but it doesn’t resolve the problem. Still, if we have many mocks, the constructor will grow, until it will become unreadable. So I kept looking. I was focused on given methods that I implemented in the fixture file. Why cannot data mocking be there? After all, “given” methods set start data. Why can’t they also set the behaviour of mocks with these data?
All mocks that I placed on constructor before, I moved into specific methods, like in the example below:
It’s a very simple solution, but it works. First of all, I remove all smell code from the constructor. Finally, the mocks were in the methods that they concern. More than that, in this solution I used only mocks that are indicated in given methods. Previously, when everything was in the constructor, I also ran everything. It’s a simple recipe for breaking old tests after adding a new mock because I didn’t notice that the new code changes the old mock setup. Now you use only what you run. Thanks to that, the maintenance and adding new tests became simpler. For the defense class of shame, I put it there after refactoring, what was my honor goal.
The next problem that appeared was work with ORM – in my case Entity Framework. Everyone who was trying to mock DbContext should know what I mean. Of course, the problem can be solved by wrapping the context in a repository and mock the abstraction. Some of the people are supporters of this solution. The others are the opposite because they think that context is a repository, so why should you wrap a repository in a repository. I don’t want to argue which approach is better.
With my implementation, the solution is very simple. Likewise a previous case, I mock with the mocking class and now the mock is adding data directly via DbContext. With mock getting the data you also use Context.
Summarizing, of course, I want to encourage you to know test driven development and my solution. But next to this, I want to get you interested in unit tests themselves. Truly, it can be something more than just writing some assertions. I know that writing new features is more exciting than test if existing ones. I also prefer this! But if you want to write unit tests, it is worth considering how you can improve them. Maybe code duplications can be less (or none) or the structure of the tests can be better. Tests really can be interesting or at least can stop being a sad responsibility.