Use Dependency Injection within NUnit
See original GitHub issueThis issue is for discussing the use of dependency injection in the internals of NUnit to improve testability and ease cross-platform support. This issue contains comments on allowing the use of dependency injection in the construction of test fixtures and in building tests, so we moved that out to a separate issue, #3220.
This is from an email thread on the developers list.
There have been a few instances recently that made me think that we should have an internal dependency injection framework with NUnit, but working on Portable has really driven home the point. It would be very nice to be able to inject platform specific implementations of interfaces into the framework from the platform specific runners.
I am thinking a NInject configuration in code type syntax. Something like
Bind<ILogger>().To<FileLogger>();
To keep it simple, we don’t need to do full constructor injection. We could just do something like the following in code.
var log = Kernel.Get<ILogger>();
We could add simple scope semantics to allow for new object per call, per thread or as a singleton.
Some of the places that it would be useful are,
- Logging/Error output - From certain places in the code, it is very hard to surface errors that are seen by the user. We currently have an InternalTraceWriter, but it would be nice to have some sort of ILogger injected in from the runner. We could inject multiple loggers to log to a file, output to the console, etc.
- PlatformAttribute - In the portable framework, much of the information we need isn’t available. It would be nice to inject classes that could provide that information.
- Runners - We are now down to one runner, but we have talked about a v2 runner. It would be nice to be able to inject runners, we could iterate through available runners to determine which support a given test assembly and run tests in the appropriate runner.
I am sure there are more opportunities. I don’t think it would be hard to write a limited dependency injection framework that satisfies just our needs and would be willing to take on the task.
I am not sure how this would work with our attempt to separate the runners from the framework via the engine, but I think it is worth exploring.
What do you think? Has anyone else been thinking along these lines? If so, how did you think we would tackle it?
@CharliePoole responded,
In general, I think it’s a pretty good idea. That said, I’d be fairly well tempted to consign the issue to ‘Future’ because we have such a lot of work to complete for a 3.0 release as it is. I’ve already pushed the expected Beta date to the end of the year and even that may be optimistic. I’d feel more positive about this as an immediate task if there were some existing blocking issues it resolved.
On specific points:
- Remember that InternalTraceWriter is designed as completely adhoc so that it won’t interfere with any logging or tracing used by the user. It’s for us, not really for users.
- We decided to eliminate capture of external (user) loggers and tracing because we felt it wasn’t the job of our framework.
- Platform attribute is definitely an issue.
- Not sure what you mean by the phrase “v2 runner” - is it the planned driver for v2? That’s not in the framework (which is what I thought you were talking about here) but the engine. I planned it as a plugin and was going to use mono.addins. Of course, that plan dates back many years and I want to look at what has come along since then. However, AFAIK, DI frameworks don’t generally do everything mono-addins does. I’m hoping to get very soon to a point where I can work on that.
As I was writing this I realized that I wasn’t sure what layer you meant it to apply to. Framework, Engine or Runner(s)?
A further thought: If we used (say) NInject at the framework level and the user was using it too, what would be the consequence?
It’s possible conflicts like this that have made me want to stay away from any 3rd party software the user might also want to use at the framework level.
@rprouse responded
I am fine leaving it to future, I just wanted to get the idea out there.
As to where it would be used, ideally, I would like the runner layer to setup the injection which would be consumed by the other layers. I’m not sure how we do that and maintain dependency separation between the layers though.
Something like NInject would be perfect, but I think you are right about potential conflicts. That is why log4net was removed, wasn’t it? I was thinking of a lighter weight framework written by us, or possibly pulled into our code and put in a new namespace so it won’t conflict. mono.addins might give us much of what we want. I am not familiar with it, but it might be a good place to start.
Issue Analytics
- State:
- Created 9 years ago
- Reactions:5
- Comments:12 (5 by maintainers)
Folks, has there been any recent discussion on this topic?
I have to say… it would be incredibly interesting to allow test classes to have injected dependencies, especially for helper/factory/etc code that is used in tests.
Usually, people will just create static classes in the test project and access them directly inside test methods, but this is far from ideal especially when you are dealing with configuration settings in more complex test projects (automation/UI tests for one).
It would be incredibly interesting to have support for interface registration/injection at the core of the test engine, as that would potentially allow us to write much more maintainable test projects.
I don’t see this being used a lot on pure unit tests, but for more elaborate tests it could for sure be leveraged.
I was even wondering if I could use
TestFixture
parameters with a customTestFixtureSource
to acomplish this. My only gripe is that it is not seamless (i.e. users will have to remember to add the attribute everywhere and such). Additionally, it would obviously cause a massive conflict with the “true” intent of parameterizedTestFixture
s.EDIT: Now that I think about it, even the assertion engine itself could be injected with this approach. It would be quite interesting moving away from a static
Assert
class into a cleaner interface-based approach. Having interface-based assertion could even allow people to switch between different assertion frameworks somewhat seamlessly oOThis can be easily done with a custom attribute that implements ITestBuilder. I’m using a NUnitTestCaseBuilder() below but it can be replaced with an instance of TestCaseAttribute or something if you don’t like the idea of using classes in the Internal namespace. I’m just doing straight DI here, but BuildFrom() could easily be modified to check for other attributes (like “Category”).