Skip to Content
Course content

98: Unit Testing with NUnit

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've probably written enough code to realize that manually running your app and clicking through menus to see if a specific edge case works is a nightmare. That's where NUnit comes in. It's one of the most popular testing frameworks in the .NET ecosystem, and once you get the hang of it, you'll feel significantly more confident hitting "deploy."

How do I actually get an NUnit test project running?

First off, you don't put your tests in your main project. You create a separate project in your solution—usually named something like YourProject.Tests. You'll need a few NuGet packages to make this work: nunit (the framework), NUnit3TestAdapter (so Visual Studio/JetBrains can actually "see" the tests), and Microsoft.NET.Test.Sdk.

Once those are in, you just mark your methods with the [Test] attribute. That's it. The test runner looks for that attribute and knows it needs to execute that specific method. Here is a simple starting point using a DiscountCalculator class we're pretending exists in your main project:

using NUnit.Framework;

[TestFixture]
public class DiscountCalculatorTests
{
    [Test]
    public void ApplyDiscount_ShouldReducePrice()
    {
        // This is a basic test
        var calculator = new DiscountCalculator();
        var result = calculator.ApplyDiscount(100m, 0.2m); // 20% off 100
        
        Assert.That(result, Is.EqualTo(80m));
    }
}

What's the difference between the "Classic" and "Constraint" Assert styles?

If you look at old tutorials, you'll see Assert.AreEqual(expected, actual). That's the "Classic" style. NUnit also has a "Constraint" model, which is Assert.That(actual, Is.EqualTo(expected)).

I strongly recommend using the Constraint model. Why? Because it reads like a sentence. When you're dealing with more complex assertions—like checking if a string starts with a certain letter or if a collection contains a specific item—the constraint model is much more flexible and intuitive. For example, Assert.That(result, Is.GreaterThan(0)) is way more natural than trying to find a specific "AssertGreaterThan" method.

How do I test ten different scenarios without writing ten different methods?

Writing a new method for every single single input value is a waste of your time. This is where [TestCase] is a lifesaver. It allows you to parameterize your tests. You define the inputs and the expected output right in the attribute, and NUnit runs the test method once for every case you provide.

Let's apply this to our DiscountCalculator to test a few different discount tiers:

[TestCase(100, 0.1, 90)]  // 10% off 100 = 90
[TestCase(100, 0.5, 50)]  // 50% off 100 = 50
[TestCase(0, 0.1, 0)]     // 10% off 0 = 0
[TestCase(100, 0, 100)]   // 0% off 100 = 100
public void ApplyDiscount_VariousInputs_ReturnsExpectedPrice(decimal price, decimal discount, decimal expected)
{
    var calculator = new DiscountCalculator();
    var result = calculator.ApplyDiscount(price, discount);
    
    Assert.That(result, Is.EqualTo(expected));
}

What exactly is "Arrange, Act, Assert" and why does everyone talk about it?

You'll hear the "AAA" pattern constantly. It's not a hard rule enforced by the compiler, but it's a discipline that keeps your tests from becoming a jumbled mess. If your tests are disorganized, you'll spend more time debugging the test than debugging the actual code.

  • Arrange: Set up your objects. Initialize the class you're testing and prepare the input data.
  • Act: Call the actual method you're testing. This should usually be a single line of code.
  • Assert: Verify that the outcome is what you expected.

I usually leave a blank line between these three sections. It makes the test instantly scannable for anyone else (including future you) who has to read it six months from now.

[Test]
public void CalculateTax_ValidAmount_ReturnsCorrectTax()
{
    // Arrange
    var taxCalc = new TaxCalculator(0.05m); 
    var amount = 100m;

    // Act
    var result = taxCalc.GetTax(amount);

    // Assert
    Assert.That(result, Is.EqualTo(5m));
}



📋 Practical Task

Exercise: Building a Loyalty Point Validator Test Suite

You are tasked with testing a new LoyaltyProgram class. The business logic is as follows: users earn 1 point for every $10 spent, but if they are a "Gold" member, they earn 2 points for every $10 spent. Spending less than $10 earns 0 points.

Your Task:

  1. Create a LoyaltyProgram class with a method CalculatePoints(decimal spend, bool isGoldMember) that implements the logic above.
  2. Create an NUnit test project and write a test class named LoyaltyProgramTests.
  3. Implement a [TestCase] method that verifies at least five different scenarios:
    • A standard member spending $50 (Should be 5 points).
    • A Gold member spending $50 (Should be 10 points).
    • A standard member spending $5 (Should be 0 points).
    • A Gold member spending $5 (Should be 0 points).
    • A standard member spending $105 (Should be 10 points - assume floor rounding).
  4. Ensure your test method follows the Arrange, Act, Assert pattern.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.