# Writing Tests

Name each test for its scenario, write it in three parts (Arrange, Act, Assert), check results with MSTest's Assert methods, and run one test for many inputs with DataRow.

Apprentice · Unit Testing · C# · Updated November 3, 2026

## After this lesson you can

- Write a test in three parts, Arrange, Act and Assert, named for its scenario
- Check results with Assert methods such as AreEqual, IsTrue, IsFalse and IsNull
- Run one test for several inputs with \[DataRow\]

## Key takeaways

- Name each test for its scenario, as `MethodName_WhenThisIsTrue_ExpectedResult`, such as `ChangeChannel_WhenOff_KeepsTheChannel`.
- Arrange sets up the starting state, Act calls the one method being tested, and Assert checks both the return value and the object's properties.
- `Assert` has a method for each kind of check, such as `AreEqual`, `IsTrue`, `IsFalse` and `IsNull`. When a check fails, the test fails.
- In `Assert.AreEqual`, put the expected value first and the actual value second, and add a message that explains the requirement.
- `[DataRow]` runs one test once for each input, and each run counts as a test of its own.

Each row of your scenario table becomes one test method. Here's how to write them so they're easy to read, and so a failure tells you exactly what broke.

## Name the scenario

A test's name should read like the scenario. This course names tests in three parts, separated by underscores:

```text
MethodName_WhenThisIsTrue_ExpectedResult
```

```csharp
ChannelUp_WhenOn_GoesUpOneChannel
ChannelUp_AtTheHighestChannel_GoesBackToTheLowest
ChangeChannel_WhenOff_KeepsTheChannel
```

Long names are fine. When one of a hundred tests fails, its name is the first thing you see, and `ChangeChannel_WhenOff_KeepsTheChannel` tells you far more than `Test7`.

## Arrange, Act, Assert

Every test has three parts, in this order:

| Part | What it does |
|---|---|
| **Arrange** | Sets up the scenario's starting state |
| **Act** | Calls **the one** method being tested |
| **Assert** | Checks the result: the return value, and the object's properties |

```csharp
[TestMethod]
public void ChannelUp_WhenOn_GoesUpOneChannel()
{
    // Arrange
    Television tv = new Television();
    tv.TurnOn();

    // Act
    int newChannel = tv.ChannelUp();

    // Assert
    Assert.AreEqual(4, newChannel, "ChannelUp returns the new channel.");
    Assert.AreEqual(4, tv.CurrentChannel, "The TV is now on the new channel.");
}
```

Notice the Assert part checks **both** the returned value and the property. A method that returns 4 but leaves the TV on channel 3 would pass the first check and fail the second.

Another scenario, starting from a different state:

```csharp
[TestMethod]
public void ChannelUp_AtTheHighestChannel_GoesBackToTheLowest()
{
    // Arrange
    Television tv = new Television();
    tv.TurnOn();
    tv.ChangeChannel(15);

    // Act
    int newChannel = tv.ChannelUp();

    // Assert
    Assert.AreEqual(3, newChannel);
}
```

## The Assert class

MSTest's `Assert` class has a method for each kind of check. If the check fails, the test fails, and the runner shows why.

| Method | Passes when |
|---|---|
| `Assert.AreEqual(expected, actual)` | The two values are equal |
| `Assert.AreNotEqual(notExpected, actual)` | The two values are different |
| `Assert.IsTrue(condition)` | The condition is `true` |
| `Assert.IsFalse(condition)` | The condition is `false` |
| `Assert.IsNull(value)` | The value is `null` |
| `Assert.IsNotNull(value)` | The value isn't `null` |
| `Assert.Contains(part, text)` | The text contains the part |

Two habits make failures easy to read:

- **Expected first, actual second**, in `AreEqual`. The failure message labels them that way.
- **Add a message** that explains the requirement. It's printed when the test fails.

Here's a test that expects the wrong thing, and what the runner says:

```csharp
Assert.AreEqual(5, tv.CurrentChannel, "Because the TV started on channel 3.");
```

```text
Failed ChannelUp_WhenOn_GoesUpOneChannel
  Error Message:
   Assert.AreEqual failed. Expected:<5>. Actual:<4>. 'expected' expression: '5', 'actual' expression: 'tv.CurrentChannel'. Because the TV started on channel 3.
```

## Many inputs, one test: DataRow

Some scenarios are the same test with different inputs. Instead of copying the test, give it parameters and one **`[DataRow]`** per input:

```csharp
[TestMethod]
[DataRow(3)]
[DataRow(7)]
[DataRow(15)]
public void ChangeChannel_ToAChannelThatExists_ChangesIt(int channel)
{
    // Arrange
    Television tv = new Television();
    tv.TurnOn();

    // Act
    bool changed = tv.ChangeChannel(channel);

    // Assert
    Assert.IsTrue(changed);
    Assert.AreEqual(channel, tv.CurrentChannel);
}
```

The runner runs it once per `DataRow`, and counts each run as a test of its own, so this method counts as three tests. A `DataRow` can hold several values, one per parameter: `[DataRow("banana", 3)]` for `(string text, int expected)`.

## Check yourself

3 questions to try on the lesson page.

## So what

A good test reads like a sentence: its name says the scenario, Arrange sets it up, Act does one thing, and Assert checks what the requirement promised. Write them that way, and a failing test tells you what's wrong before you open the code.

## Key terms

- [Test Naming](https://www.ask-codey.com/glossary/test-naming)
- [Arrange, Act, Assert](https://www.ask-codey.com/glossary/arrange-act-assert)
- [Assertion](https://www.ask-codey.com/glossary/assertion-unit-testing)
- [\[DataRow\]](https://www.ask-codey.com/glossary/datarow)

## Cheat sheets

- [Writing Tests](https://www.ask-codey.com/cheat-sheets/unit-testing-with-mstest-writing-tests-cheat-sheet)

Source: https://www.ask-codey.com/lessons/unit-testing-with-mstest-writing-tests
