I've been reading quite a lot about TDD, especially about various practices and experiences, dos and don'ts and I'm still puzzled about several aspects after trying to apply it on a Spring Boot application with persistence and REST, all packaged by feature.
Many blog posts and answers here on StackOverflow suggest that we should test the interface and not the implementation. However, examples, like those in "Growing Object-Oriented Software, Guided by Tests" by Steve Freeman and Nat Pryce, suggest quite a different approach, since all of them test implementations mostly, asserting number of method calls, etc. So our tests actually become dependent on the implementation itself. Of course, we can use inversion of control - pass the dependencies via constructors and mock them out in our tests, but is there really a point in mocking, let's say, a CRUD repository?
Maybe I'll give you a simple example to better picture this situation. Let's say we have a multipage user information form, and each form page has to be saved separately.
So, the planned structure of the information package could be like this following package-by-feature approach:
com
.. example
.... user
...... information
........ basic
.......... + BasicInformationDto
.......... + BasicInformationService
.......... - BasicInformationServiceImpl
.......... - BasicInformationDao
.......... - BasicInformationRepository
........ additional
.......... + AdditionalInformationDto
.......... + AdditionalInformationService
.......... - AdditionalInformationServiceImpl
.......... - AdditionalInformationDao
.......... - AdditionalInformationRepository
........ + InformationRestController
+ is public and - is default access modifier
My first idea:
- Write a simple REST integration test to test mappings provided by InformationRestController.
- Create InformationRestController with those mappings.
- Create test for BasicInformationService, create a BasicInformationServiceImpl object, mock BasicInformationRepository and test if repository's save method gets called exactly once.
- Create classes for BasicInformationService, BasicInformationServiceImpl and BasicInformationRepository with BasicInformationDao.
- Refactor, implement details, dto and dao fields, etc.
What if I later decide to use repository`s #saveAndFlush? Is it normal that a unit test needs to be refactored because of such a small change? And if I test only the interface, how do I test different implementations with different dependencies implementing it?
So how, using TDD, would one design, for example, the BasicInformationService`s save method if it only maps a DTO object to a DAO and then persists it using BasicInformationRepository?