This question is a follow-up question to Clean Architecture use case testing.
Suppose the production code injects Use Case Interactor
into the Input Boundary
- that happens somewhere in the main component1. But now I want to test the Input Boundary
/Output Boundary
of the use case. I can only think of two ways to do this:
- Recreate the dependency injection setup at the beginning of the test - this forces the test to depend on
Use Case Interactor
. - Depend on the main component for DI - this seems risky because the main component is volatile.
Is there a clean way to handle this situation?
1 Clean Architecture Chapter 26, Robert C Martin
2 Answers 2
How can I manage dependency injection in test code?
The same way you manage it in production code.
Suppose the production code injects Use Case Interactor into the Input Boundary
Then production code is confused because you don't inject classes into interfaces.
But now I want to test the Input Boundary/Output Boundary of the use case
Stop that. Test behavior not structure. The boundaries do not hold behavior. They define the mini language that can be used to communicate across the boundary. Tests should use that same mini language to test what's across the boundary.
I think it would be best to test UseCaseInteractor directly, rather than throught the boundaries. @Brady Dean
Using UseCaseInteractor
through the boundary is the direct way. Anything else is sneaking past the abstraction you're meant to use. Tests that sneak in the back door only prove that the back door works.
Is there a clean way to handle this situation?
Focus on the behavior you expect when you use the unit under test in the way it's meant to be used. Don't worry about isolation unless that's needed to make the unit's behavior deterministic and fast. Don't go sneaking around abstractions. And stop treating behaviorless structure code as something that needs to be tested.
-
It's apparent I didn't ask the question clearly enough. But now I want to test the Input Boundary/Output Boundary of the use case should have been But now I want to test the use case through the IO Boundaries (which you did well describing as the "direct way"). As for the dependency injection stuff - I had in mind something like ASP.NET Core's IOC container where you ask for an
IInputBoundary
and are given aUseCaseInteractor
object. I suppose that for the purpose of testing, these objects should just be created by hand.Brady Dean– Brady Dean08/29/2021 19:31:17Commented Aug 29, 2021 at 19:31
The input boundary is a simple abstraction to prevent the controller from knowing about the specific use case interactor. It doesn’t do anything, it simply invokes the interactor.
If your language supports generics, you can makes this a generic class. In your test, create a fake interactor and use that to test the generics.
Personally I use the Mediatr library (C#). All interactors implement an IRequestHandler interface and input data implements IRequest. Inject Mediatr into the controller, create a new request and call mediator.Send(request);. It doesn’t get much cleaner than that and because you can rely on a library you don’t even have to write and test the glue code yourself!
-
I think my concern is with how coupled the tests should be to the internals of the use case. I think the test ought to be independent of the specific use case implementation, but DI forces a decision to be made before the test can execute.Brady Dean– Brady Dean07/29/2021 18:42:59Commented Jul 29, 2021 at 18:42
-
specificInteractor.Handle(request)
- clean one line without dependencies on third party libraries(Mediatr) ;)Fabio– Fabio07/29/2021 20:08:16Commented Jul 29, 2021 at 20:08
Explore related questions
See similar questions with these tags.
InputBoundary
in isolation, or do you want to create an integration test forInputBoundary
andUseCaseInteractor
?InputBoundary
which depends onUseCaseInteractor
- so to testInputBoundary
you must pass instance ofUseCaseInteractor
. It is ok for test to depend on input parameters (UseCaseInteractor
is an input parameter ofInputBoundary
which passed via constructor).UseCaseInteractor
as a constructor argument (I assume)