question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Use Dependency Injection within NUnit

See original GitHub issue

This 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:open
  • Created 9 years ago
  • Reactions:5
  • Comments:12 (5 by maintainers)

github_iconTop GitHub Comments

5reactions
julealgoncommented, Feb 9, 2018

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 custom TestFixtureSource 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 parameterized TestFixtures.

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 oO

4reactions
Myndalecommented, Jun 20, 2018

This 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”).

	// Method Injection using NUnit/Ninject

	[TestFixture]
	public class MyTests
	{
		// the test method that needs DI
		[Test]
		[CustomInjection]
		public void My_Test_Method(IInterfaceA a, IInterfaceB b)
		{
			// a and b get injected here
			Assert.AreEqual(a.GetType(), typeof(ImplementationA));
			Assert.AreEqual(b.GetType(), typeof(ImplementationB));
		}
	}

	// custom attribute invokes the DI kernel to create parameters for method injection
	public class CustomInjectionAttribute : Attribute, ITestBuilder
	{
		private static IKernel Kernel { get; } = new StandardKernel(new MyModule());

		public IEnumerable<TestMethod> BuildFrom(IMethodInfo method, Test suite)
		{
			// create method arguments using dependency injection
			var arguments = method.MethodInfo.GetParameters()
				.Select(p => Kernel.Get(p.ParameterType))
				.Cast<object>()
				.ToArray();
			yield return new NUnitTestCaseBuilder()
				.BuildTestMethod(method, suite, new TestCaseParameters(arguments));
		}
	}

	// custom module for specifying DI bindings
	public class MyModule : NinjectModule
	{
		public override void Load()
		{
			Bind<IInterfaceA>().To<ImplementationA>();
			Bind<IInterfaceB>().To<ImplementationB>();
		}
	}

	public interface IInterfaceA { }
	public class ImplementationA : IInterfaceA { }

	public interface IInterfaceB { }
	public class ImplementationB : IInterfaceB { }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Dependency Injection in Nunit test classes via contructor or ...
This is my self-education activities, I want just to figure out the possibility of using constructor and property injection in nunit test ...
Read more >
kalebpederson/nunit.dependencyinjection
0.7.0 - Introduces a NUnit.Extension.DependencyInjection.Abstractions assembly to ease the process of creating convention based type discoverers while ensuring ...
Read more >
NUnit.Extension.DependencyInjection 0.7.1
Enables dependency injection within NUnit. Dependency injection support is container specific and provided by separate NuGet packages.
Read more >
Using Dependency Injection with Selenium, .NET Core 6 and ...
This strategy is helpful when automating large applications. It is essential to create a test automation framework which is simple, robust and ...
Read more >
Dealing with dependencies
The main concern is that unit tests should be focusing on the system under test and not leak into testing the behavior of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found