DI and tests
Set up DI container and xUnit tests.
DI and tests
Reflection discovery works; but we still instantiate commands by hand with Activator.CreateInstance. Now we add a tiny dependency injection container: a Container class with Register<T>(string, Func<T>) for registration and Resolve<T>(string) for resolving instances.
We also add a small test: a TestHelloCommand method resolves the "hello" command from the container, runs it, and checks the result is "Merhaba". This is a hand-rolled assertion standing in for xUnit; it keeps the project in a single file.
Hand-rolled test
In real projects tests live in a separate assembly and use xUnit. To stay in one file we write a static TestHelloCommand method: it resolves the command from the container, gets the result via ExecuteAsync().Result, and verifies with if (result != "Merhaba") throw new Exception(...). On success it writes Console.WriteLine("Test passed: hello").
This mimics what a real test framework does, at small scale: validation, exception on failure, notification on success. It is a practical way to keep the unit-testing habit in a single file.
Exercise Add Container and a test
Define a Container class with a private Dictionary<string, Func<ICommand>> _factories. A Register<T>(string name, Func<T> factory) where T : class, ICommand method stores the factory; a Resolve<T>(string name) where T : class method calls the factory and casts the result to (T). In Main, create a Container, register with Register<ICommand>("hello", () => new HelloCommand()), call TestHelloCommand(container), then resolve the command with Resolve<ICommand>("hello") and print await cmd.ExecuteAsync(). TestHelloCommand resolves the command via Resolve<ICommand>("hello"), gets the result via ExecuteAsync().Result, throws new Exception(...) if it is not "Merhaba", else writes Console.WriteLine("Test passed: hello").
Show example solution
Write class Container { private readonly Dictionary<string, Func<ICommand>> _factories = new(); public void Register<T>(string name, Func<T> factory) where T : class, ICommand => _factories[name] = () => factory(); public T Resolve<T>(string name) where T : class => (T)_factories[name](); }. In Main: var container = new Container(); container.Register("hello", () => new HelloCommand()); TestHelloCommand(container); var cmd = container.Resolve<ICommand>("hello"); Console.WriteLine(await cmd.ExecuteAsync()); static void TestHelloCommand(Container container) { var cmd = container.Resolve<ICommand>("hello"); var result = cmd.ExecuteAsync().Result; if (result != "Merhaba") throw new Exception($"Expected: Merhaba, got: {result}"); Console.WriteLine("Test passed: hello"); }
Example solution:
using System;
using System.Threading.Tasks;
using System.Reflection;
using System.Collections.Generic;
[Command("hello")]
class HelloCommand : ICommand
{
public async Task<string> ExecuteAsync()
{
await Task.CompletedTask;
return "Merhaba";
}
}
class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name) => Name = name;
}
interface ICommand
{
Task<string> ExecuteAsync();
}
class Container
{
private readonly Dictionary<string, Func<ICommand>> _factories = new();
public void Register<T>(string name, Func<T> factory) where T : class, ICommand =>
_factories[name] = () => factory();
public T Resolve<T>(string name) where T : class => (T)_factories[name]();
}
class Program
{
static async Task Main()
{
var container = new Container();
container.Register("hello", () => new HelloCommand());
TestHelloCommand(container);
var cmd = container.Resolve<ICommand>("hello");
Console.WriteLine(await cmd.ExecuteAsync());
}
static void TestHelloCommand(Container container)
{
var cmd = container.Resolve<ICommand>("hello");
var result = cmd.ExecuteAsync().Result;
if (result != "Merhaba")
throw new Exception($"Expected: Merhaba, got: {result}");
Console.WriteLine("Test passed: hello");
}
}
Test yourself
-
Complete the DI registration call
container.Register____<ICommand>("hello", () => new HelloCommand());Show answer
container.Register<ICommand><ICommand>("hello", () => new HelloCommand()); -
What does Container.Resolve<T>("hello") return?
- The HelloCommand type
- An ICommand instance built by the registered factory
- A Type object
- A null reference
Show answer
A Type object — Correct. Resolve<T> calls the registered factory and returns an ICommand instance.
-
Match DI and test terms
- Container
- Registration and resolution box
- Register<T>
- Factory registration
- Resolve<T>
- Instance resolution
- TestHelloCommand
- Verification method