DEV Community

loading...

Discussion on: Test Driven Development by Example

Collapse
adnanhz profile image
Adnan

Nice Article Nicola! I'd like to add my own little experience with TDD. It also didn't click right away for me.

I usually mostly code web-apps which involve database transactions and few "algorithms" - think for example a blog where you just need to post and fetch articles.

I naturally thought that the best way was to do what Laravel calls "feature tests" which assert the result of an API call.
Then, as I progressed, I learned that in some cases I also need to run tests outside of API calls, so I started writing what would technically be called "Integration tests".

Today, I've reached a stage where TDD has helped me greatly in decoupling my code into small, readable functions and classes.

The final thing I haven't come to a conclusion yet is using mocks to replace external dependencies when testing my code. I think it has to do with the nature of my work which highly involve storing and fetching data to/from the database without much "algorithms" to be tested per se.
I don't think the hassle of implementing something like the repository pattern just to get that extra layer between the database and the ORM so that I can mock/ditch the ORM.
I'm so far still saving myself a good amount of time by spinning up the whole process on a test database and still using the ORM on my "unit" tests. If I had to follow it by the book, I should be mocking every single external dependency.

Do you agree with me, or do you think I haven't caught on writing unit tests with mocks quite yet?

Collapse
napicella profile image
Nicola Apicella Author • Edited

Thank you!

Your question is interesting and a bit open ended. I'll give you my 2 cents on mocking the datastore (via repository interface or similar).
Most of the code I have seen does use some interface between the business logic and the datastore. And I think the reason is: you want to have a way to test that the code that uses the repository works as expected even at the boundaries; for example: am I catching that SQLException? Do I fallback to a different replica? What happens if the answer from the datastore does not contain the field I was expecting? Can I handle multiple version of the item?
All these things are hard to set up with an integration test.

This is why I think using an interface is useful, but depending on who you ask, you might also get as answers:

  • I use an interface 'cause I do not want to spin up a database to run this test. It's slow.
  • I use an interface 'cause I want to keep the possibility of switching datastore in the future

I do not completely agree with those because:

  • Today, spinning up a database or anything is very cheap - this gives you one less reason to mock it.
  • Without an integration test that covers the database you lose confidence that the program does indeed work
  • Changing datastore is complicated. If you need to do a data migration, not having an interface in place is the least of your problems

To sum it up - it depends on the use case. I tend to prefer a lightweight repository pattern because I can easily test edge conditions.

I hope I answered your question.

Collapse
adnanhz profile image
Adnan

Yep, I see where you're getting. Thanks a lot for the input. I'll keep running the database in the foreseeable future even if some people will hate me :D