Testing and DI

Back

Loading concept...

🏗️ Building Strong Code: Testing & Dependency Injection

Imagine you’re building with LEGO blocks. Each block does one job, and they snap together perfectly. That’s exactly how great C# code works!


🎯 The Big Picture

Think of your code like a restaurant kitchen:

  • Dependency Injection (DI) = The waiter brings ingredients TO the chef (instead of the chef finding them)
  • Unit Testing = Tasting each dish BEFORE serving to customers

By the end of this guide, you’ll build code that’s:

  • ✅ Easy to test
  • ✅ Easy to change
  • ✅ Easy to understand

📦 What is Dependency Injection?

The Problem: Tight Coupling

Imagine a chef who grows their own vegetables, raises chickens, and mills flour. That chef can ONLY cook what they can produce!

// ❌ BAD: Chef makes their own ingredients
public class Chef
{
    private Oven _oven = new Oven();  // Stuck with THIS oven forever!

    public void Cook()
    {
        _oven.Bake();
    }
}

What’s wrong? The Chef is GLUED to that specific Oven. Want a different oven? You’d have to rebuild the whole Chef!


The Solution: Dependency Injection

Now imagine a chef who says: “Just GIVE me an oven, and I’ll cook!”

// ✅ GOOD: Someone GIVES the chef an oven
public class Chef
{
    private IIOven _oven;  // Any oven will do!

    public Chef(IOven oven)  // Oven is INJECTED
    {
        _oven = oven;
    }

    public void Cook()
    {
        _oven.Bake();
    }
}

Magic happens:

  • Want a gas oven? Pass a GasOven
  • Want an electric oven? Pass an ElectricOven
  • Want a fake oven for testing? Pass a MockOven

Three Ways to Inject Dependencies

graph TD A["🎁 Dependency Injection"] --> B["🏗️ Constructor Injection"] A --> C["📝 Property Injection"] A --> D["⚙️ Method Injection"] B --> E["Chef#40;IOven oven#41;"] C --> F["public IOven Oven { set; }"] D --> G["Cook#40;IOven oven#41;"]

1️⃣ Constructor Injection (Most Common!)

public class EmailService
{
    private readonly ILogger _logger;

    // Dependency comes through constructor
    public EmailService(ILogger logger)
    {
        _logger = logger;
    }
}

2️⃣ Property Injection

public class EmailService
{
    // Dependency set through property
    public ILogger Logger { get; set; }
}

3️⃣ Method Injection

public class EmailService
{
    // Dependency passed to specific method
    public void Send(ILogger logger)
    {
        logger.Log("Sending...");
    }
}

⏰ DI Lifetimes: How Long Do Objects Live?

Imagine a coffee shop:

graph TD A["☕ DI Lifetimes"] --> B["🎪 Transient"] A --> C["🎫 Scoped"] A --> D["👑 Singleton"] B --> E["New cup every order"] C --> F["Same cup per customer visit"] D --> G["ONE cup for entire shop"]

🎪 Transient: Fresh Every Time

Like ordering a new coffee cup for EVERY sip!

services.AddTransient<ICoffee, Coffee>();

// Every time you ask, you get a NEW Coffee
var coffee1 = provider.GetService<ICoffee>();
var coffee2 = provider.GetService<ICoffee>();
// coffee1 ≠ coffee2 (different objects!)

Use when: Object is cheap to create, no shared state needed.


🎫 Scoped: Once Per Request

Like getting ONE coffee cup when you sit down, using it for your whole visit.

services.AddScoped<ICustomerOrder, CustomerOrder>();

// Same order throughout ONE web request
// New request = New order

Use when: Need same data across one HTTP request (like database contexts).


👑 Singleton: One For All

Like the shop having ONE coffee machine that everyone shares.

services.AddSingleton<ICoffeeMachine, CoffeeMachine>();

// ONE instance for the ENTIRE app lifetime
// Everyone gets the SAME machine

Use when: Expensive to create, safe to share (like configuration, caching).


Quick Comparison

Lifetime Created Example
Transient Every request Email message
Scoped Once per HTTP request Database context
Singleton Once ever Configuration

🧪 Unit Testing Basics

What IS Unit Testing?

Imagine you’re making a sandwich:

  • Unit test = Check if the bread is fresh (just the bread!)
  • Integration test = Check if the whole sandwich tastes good
  • End-to-end test = Have someone eat the sandwich and ask them
graph TD A["🧪 Testing Pyramid"] --> B["🔬 Unit Tests"] A --> C["🔗 Integration Tests"] A --> D["🎯 E2E Tests"] B --> E["Many, Fast, Isolated"] C --> F["Some, Slower"] D --> G["Few, Slowest"]

Your First Unit Test

Let’s test a simple calculator:

// The code we want to test
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

// The test
[TestClass]
public class CalculatorTests
{
    [TestMethod]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange (Setup)
        var calc = new Calculator();

        // Act (Do the thing)
        int result = calc.Add(2, 3);

        // Assert (Check it worked)
        Assert.AreEqual(5, result);
    }
}

The AAA Pattern

Every good test follows Arrange-Act-Assert:

graph LR A["🎬 Arrange"] --> B["🎯 Act"] --> C["✅ Assert"] A --> D["Setup objects"] B --> E["Call the method"] C --> F["Check the result"]

Think of it like cooking:

  1. Arrange = Get your ingredients ready
  2. Act = Cook the dish
  3. Assert = Taste to make sure it’s good!

🏷️ Test Attributes: Labeling Your Tests

Test frameworks use special labels (attributes) to know what’s a test.

MSTest Attributes

[TestClass]  // "This class contains tests"
public class MyTests
{
    [TestMethod]  // "This is a test!"
    public void Test1() { }

    [TestInitialize]  // Runs BEFORE each test
    public void Setup() { }

    [TestCleanup]  // Runs AFTER each test
    public void Cleanup() { }

    [DataRow(1, 2, 3)]  // Test with different data
    [DataRow(0, 0, 0)]
    [DataTestMethod]
    public void Add_WithData_Works(int a, int b, int expected)
    {
        Assert.AreEqual(expected, a + b);
    }
}

xUnit Attributes (Popular Alternative)

public class MyTests  // No class attribute needed!
{
    [Fact]  // Simple test
    public void Test1() { }

    [Theory]  // Test with data
    [InlineData(1, 2, 3)]
    [InlineData(0, 0, 0)]
    public void Add_Works(int a, int b, int expected)
    {
        Assert.Equal(expected, a + b);
    }
}

✅ Assertions: Checking Your Work

Assertions are like a teacher checking your homework!

Common Assertions

// Check equality
Assert.AreEqual(expected, actual);
Assert.AreNotEqual(5, result);

// Check true/false
Assert.IsTrue(user.IsActive);
Assert.IsFalse(list.IsEmpty);

// Check for null
Assert.IsNull(emptyResult);
Assert.IsNotNull(createdUser);

// Check types
Assert.IsInstanceOfType(obj, typeof(Dog));

// Check collections
CollectionAssert.Contains(list, item);
CollectionAssert.AreEqual(expected, actual);

// Check exceptions
Assert.ThrowsException<ArgumentException>(
    () => calculator.Divide(1, 0)
);

Assertion Messages

Always add helpful messages!

Assert.AreEqual(
    100,
    account.Balance,
    "Balance should be 100 after deposit"
);

🎭 Putting It All Together

Here’s a real-world example combining DI and Testing:

// 1. Define an interface
public interface IEmailSender
{
    void Send(string to, string message);
}

// 2. Real implementation
public class EmailSender : IEmailSender
{
    public void Send(string to, string msg)
    {
        // Actually sends email
    }
}

// 3. Class that USES email sender (via DI)
public class OrderService
{
    private readonly IEmailSender _sender;

    public OrderService(IEmailSender sender)
    {
        _sender = sender;  // Injected!
    }

    public void PlaceOrder(Order order)
    {
        // ... process order ...
        _sender.Send(order.Email, "Order placed!");
    }
}

// 4. Test with FAKE email sender
[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public void PlaceOrder_SendsEmail()
    {
        // Arrange - use a FAKE sender
        var fakeSender = new FakeEmailSender();
        var service = new OrderService(fakeSender);
        var order = new Order { Email = "test@test.com" };

        // Act
        service.PlaceOrder(order);

        // Assert
        Assert.IsTrue(fakeSender.WasCalled);
    }
}

public class FakeEmailSender : IEmailSender
{
    public bool WasCalled { get; private set; }

    public void Send(string to, string msg)
    {
        WasCalled = true;  // Just record it was called
    }
}

🚀 Key Takeaways

Concept Remember This
DI Basics Don’t CREATE dependencies, RECEIVE them
Transient New object every time
Scoped Same object per request
Singleton One object forever
Unit Testing Test ONE thing in isolation
AAA Pattern Arrange → Act → Assert
Assertions Your code’s spell-checker

🎉 You Did It!

You now understand:

  • ✅ Why we inject dependencies (flexibility!)
  • ✅ How long DI objects live (lifetimes)
  • ✅ How to write unit tests (AAA pattern)
  • ✅ How to use test attributes and assertions

Your code will be:

  • Easier to test 🧪
  • Easier to change 🔄
  • Easier to understand 📖

Now go build something amazing! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.