CodeBlanks Logo Icon CodeBlanks

DI and tests

C# course · Advanced · Lesson 82/83

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

  1. Complete the DI registration call

    container.Register____<ICommand>("hello", () => new HelloCommand());
    Show answer
    container.Register<ICommand><ICommand>("hello", () => new HelloCommand());
  2. 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.

  3. Match DI and test terms

    Container
    Registration and resolution box
    Register<T>
    Factory registration
    Resolve<T>
    Instance resolution
    TestHelloCommand
    Verification method

All lessons in Advanced