Reputation: 2675
We have a WPF application. Many of the ViewModels use the same dependencies that have to be mocked. Sometimes, constructors of the ViewModels have too many dependencies (over-injection) exposed with the only intention - to allow unit testing. For example:
[ImportingConstructor]
public PasswordInputViewModel(
IPaymentSystemProvider provider,
IAppContext appCtx,
IEventAggregator eventAggregator,
IPromptCreator promptCreator) {
}
The point is that three of the dependencies are dependencies from the infrastructure. Exposing them just for explicit injection with the only intention to allow unit testing adds more noise than helpful information of the dependencies of the ViewModel. Especially, because almost all VMs have these dependencies.
Because of that, we moved these dependencies into the BaseViewModel
class:
public class ScreenExtended : Screen {
[Import]
public IEventAggregator eventAggregator { get; private set; }
[Import]
public IPromptCreator Prompt { get; private set; }
[Import]
public IAppContext CurrentApp { get; private set; }
}
Now, we need to mock them somehow from the unit tests evading constructor injection. So, we can bootstrap the IoC.
And now is the question: how to use IoC properly here? Bootstrap the IoC for every class, or for every test, or make it static and initialize only once? If we make it static than we need somehow recompose the IoC. What would you recommend?
Upvotes: 1
Views: 729
Reputation: 18126
Sometimes, constructors of the
ViewModel
s have too many dependencies (over-injection) exposed with the only intention - to allow unit testing.
When there are too many dependencies, then more likely the Single responsibility principle has been violated.
In the context of the provided ViewModel
, is having 4 dependencies a violation? This question has no the right answer (i.e. the subjective question). There should be a dependency balance: not too few, not too many.
The point is that three of the dependencies are dependencies from the infrastructure. Exposing them just for explicit injection with the only intention to allow unit testing adds more noise than helpful information of the dependencies of the ViewModel. Especially, because almost all VMs have these dependencies.
The recommendation to enforce Single responsibility principle is quite abstract, because more ViewModel
-classes are required to see (their usage of the dependencies): introduce an abstraction layer to encapsulate the implementation of high-level operations. It may sounds too "big", but it can be just a Facade which hides the implementation details of the high-level operations (using Infrastructure services, etc).
Now, we need to mock them somehow from the unit tests evading constructor injection. So, we can bootstrap the IoC.
Please consider using the constructor injection and implementing unit-tests without using a concrete DI-container API, unless it is absolutely required. The unit-tests should not depend on the API of the concrete DI-container.
Upvotes: 0
Reputation: 7934
I would say it depends on whether the injected items are concrete implementations or mock objects. If you are using concrete implementations, then you are likely to have state issues between tests if you initialise at the class level.
I typically reinitialise each of the dependencies per test, even the one concrete class (which mocks a service layer on top of a mock repository). When testing CRUD type functionality, it definitely helps to be able to reset to zero each test, despite the fact that it takes a little longer. Rather have a test that can be run correctly and reliably in isolation or in a list.
Upvotes: 1