.NET MSTest Framework
Introduction
Microsoft's MSTest is the built-in testing framework for .NET that allows developers to create and run automated tests for their applications. MSTest (also known as Visual Studio Test) provides a comprehensive set of tools and libraries for testing .NET code, making it an excellent choice for beginners in the .NET ecosystem.
As one of the oldest testing frameworks for .NET, MSTest is deeply integrated with Visual Studio and offers a straightforward approach to testing your applications. In this guide, we'll explore how to set up MSTest, write effective tests, and utilize its features to ensure your code works as expected.
Getting Started with MSTest
Setting Up MSTest in a Project
To get started with MSTest in your .NET project, you need to install the required NuGet packages:
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package MSTest.TestAdapter
dotnet add package MSTest.TestFramework
Alternatively, you can create a new MSTest project using the .NET CLI:
dotnet new mstest -n MyTestProject
Project Structure
A typical MSTest project structure looks like:
MyTestProject/
├── MyTestProject.csproj
└── UnitTest1.cs
The generated UnitTest1.cs
file will contain a basic test class:
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyTestProject
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
// Your test code here
}
}
}
Core MSTest Components
Test Class
In MSTest, a test class is marked with the [TestClass]
attribute and contains one or more test methods. Each test class should focus on testing a specific component or functionality of your application.
[TestClass]
public class CalculatorTests
{
// Test methods go here
}
Test Methods
Test methods are marked with the [TestMethod]
attribute and contain the code to verify that your application behaves as expected.
[TestMethod]
public void Add_TwoNumbers_ReturnsSum()
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(5, 3);
// Assert
Assert.AreEqual(8, result);
}
Setup and Teardown
MSTest provides attributes for setup and teardown operations:
[TestInitialize]
: Runs before each test method[TestCleanup]
: Runs after each test method[ClassInitialize]
: Runs once before any tests in the class[ClassCleanup]
: Runs once after all tests in the class
Here's an example:
[TestClass]
public class DatabaseTests
{
private Database _db;
[TestInitialize]
public void Setup()
{
_db = new Database();
_db.Connect();
}
[TestCleanup]
public void Cleanup()
{
_db.Disconnect();
}
[ClassInitialize]
public static void ClassSetup(TestContext context)
{
// Setup shared resources
}
[ClassCleanup]
public static void ClassCleanup()
{
// Clean up shared resources
}
[TestMethod]
public void TestDatabaseOperation()
{
// Test code
}
}
Writing Effective MSTest Tests
Assertions
MSTest provides a rich set of assertion methods through the Assert
class:
[TestMethod]
public void AssertionDemo()
{
// Basic equality
Assert.AreEqual(42, CalculateValue());
// Reference equality
Assert.AreSame(expectedObject, actualObject);
// Boolean assertions
Assert.IsTrue(condition);
Assert.IsFalse(condition);
// Null checks
Assert.IsNull(value);
Assert.IsNotNull(value);
// Type checks
Assert.IsInstanceOfType(value, typeof(string));
// Exception testing
Assert.ThrowsException<ArgumentNullException>(() => MethodThatThrows(null));
// String assertions
Assert.AreEqual("expected", actual, ignoreCase: true);
Assert.IsTrue(string.Contains("substring"));
// Collection assertions
CollectionAssert.AreEqual(expectedCollection, actualCollection);
CollectionAssert.Contains(collection, item);
}
Testing Exceptions
To test that a method properly throws an expected exception:
[TestMethod]
public void Divide_ByZero_ThrowsDivideByZeroException()
{
// Arrange
var calculator = new Calculator();
// Act & Assert
Assert.ThrowsException<DivideByZeroException>(() => calculator.Divide(10, 0));
}
Data-Driven Tests
MSTest supports data-driven tests through the [DataRow]
attribute, allowing you to run the same test with different inputs:
[DataTestMethod]
[DataRow(1, 1, 2)]
[DataRow(5, 3, 8)]
[DataRow(0, 0, 0)]
[DataRow(-1, -1, -2)]
public void Add_WithVariousInputs_ReturnsSum(int a, int b, int expected)
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(a, b);
// Assert
Assert.AreEqual(expected, result);
}
Real-World MSTest Example
Let's look at a more comprehensive example testing a ShoppingCart
class:
First, our implementation class:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ShoppingCart
{
private List<Product> _items = new List<Product>();
public IReadOnlyList<Product> Items => _items.AsReadOnly();
public void AddItem(Product product)
{
if (product == null)
throw new ArgumentNullException(nameof(product));
_items.Add(product);
}
public void RemoveItem(int productId)
{
var product = _items.FirstOrDefault(p => p.Id == productId);
if (product != null)
_items.Remove(product);
}
public decimal CalculateTotal()
{
return _items.Sum(item => item.Price);
}
public void Clear()
{
_items.Clear();
}
}
Now, let's write tests for our ShoppingCart
:
[TestClass]
public class ShoppingCartTests
{
private ShoppingCart _cart;
private Product _product1;
private Product _product2;
[TestInitialize]
public void Setup()
{
_cart = new ShoppingCart();
_product1 = new Product
{
Id = 1,
Name = "Test Product 1",
Price = 10.00m
};
_product2 = new Product
{
Id = 2,
Name = "Test Product 2",
Price = 15.50m
};
}
[TestMethod]
public void AddItem_ValidProduct_AddsToCart()
{
// Act
_cart.AddItem(_product1);
// Assert
Assert.AreEqual(1, _cart.Items.Count);
Assert.AreEqual(_product1, _cart.Items[0]);
}
[TestMethod]
public void AddItem_NullProduct_ThrowsArgumentNullException()
{
// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => _cart.AddItem(null));
}
[TestMethod]
public void RemoveItem_ExistingProduct_RemovesFromCart()
{
// Arrange
_cart.AddItem(_product1);
_cart.AddItem(_product2);
// Act
_cart.RemoveItem(_product1.Id);
// Assert
Assert.AreEqual(1, _cart.Items.Count);
Assert.AreEqual(_product2, _cart.Items[0]);
}
[TestMethod]
public void RemoveItem_NonexistentProduct_DoesNothing()
{
// Arrange
_cart.AddItem(_product1);
// Act
_cart.RemoveItem(999); // Non-existent product ID
// Assert
Assert.AreEqual(1, _cart.Items.Count);
}
[TestMethod]
public void CalculateTotal_MultipleProducts_ReturnsSumOfPrices()
{
// Arrange
_cart.AddItem(_product1); // $10.00
_cart.AddItem(_product2); // $15.50
// Act
decimal total = _cart.CalculateTotal();
// Assert
Assert.AreEqual(25.50m, total);
}
[TestMethod]
public void Clear_WithProducts_EmptiesCart()
{
// Arrange
_cart.AddItem(_product1);
_cart.AddItem(_product2);
// Act
_cart.Clear();
// Assert
Assert.AreEqual(0, _cart.Items.Count);
}
}
Advanced MSTest Features
Test Categories
You can categorize tests using the [TestCategory]
attribute:
[TestMethod]
[TestCategory("Integration")]
public void DatabaseConnection_ValidCredentials_ConnectsSuccessfully()
{
// Test code
}
This allows you to run specific categories of tests:
dotnet test --filter "TestCategory=Integration"
Ignoring Tests
Sometimes you may need to temporarily disable a test:
[TestMethod]
[Ignore("Fix this test after updating the API")]
public void TemporarilyBrokenTest()
{
// Test code
}
Timeout
You can set a timeout for tests that should complete within a specific time:
[TestMethod]
[Timeout(1000)] // 1000 milliseconds
public void TimeLimitedTest()
{
// Test code that should complete quickly
}
Expected Exceptions
Besides Assert.ThrowsException
, you can also specify expected exceptions with the [ExpectedException]
attribute:
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void Divide_ByZero_ThrowsException()
{
var calculator = new Calculator();
calculator.Divide(10, 0); // Should throw DivideByZeroException
}
Testing Asynchronous Code
MSTest supports testing asynchronous methods:
[TestMethod]
public async Task FetchDataAsync_ValidRequest_ReturnsData()
{
// Arrange
var client = new DataClient();
// Act
var result = await client.FetchDataAsync("api/data");
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("expected data", result.Value);
}
Running MSTest Tests
Running Tests from the Command Line
Use the .NET CLI to run your tests:
# Run all tests in the project
dotnet test
# Run specific tests
dotnet test --filter "FullyQualifiedName~ShoppingCartTests"
# Run tests with a specific category
dotnet test --filter "TestCategory=UnitTest"
Running Tests in Visual Studio
In Visual Studio, you can run tests from the Test Explorer window, which offers features like:
- Running all tests
- Running specific tests
- Debugging tests
- Viewing test results and output
Best Practices for MSTest
-
Follow the AAA Pattern: Arrange, Act, Assert
csharp[TestMethod]
public void Example_AAAPattern()
{
// Arrange
var sut = new SystemUnderTest();
// Act
var result = sut.MethodToTest();
// Assert
Assert.AreEqual(expected, result);
} -
Name tests clearly: Use a naming convention like
MethodName_Scenario_ExpectedBehavior
-
Keep tests independent: Tests shouldn't depend on each other's state or execution order
-
One assertion concept per test: Focus each test on verifying one specific behavior
-
Use test doubles (mocks/stubs): To isolate the system under test from external dependencies
-
Test edge cases: Include boundary conditions and error scenarios
-
Keep tests fast: Slow tests discourage frequent test runs
Summary
MSTest is a powerful and integrated testing framework for .NET applications that provides:
- Easy setup and integration with Visual Studio
- Comprehensive assertion capabilities
- Support for test initialization and cleanup
- Features for data-driven testing
- Tools for categorizing and filtering tests
- Support for asynchronous code testing
By following the patterns and practices outlined in this guide, you can create effective tests that help ensure your .NET applications work correctly and reliably.
Additional Resources
- Official MSTest Documentation
- Microsoft Learn: Unit test C# code in .NET Core using dotnet test and MSTest
- MSTest on GitHub
Practice Exercises
- Create a test project for a simple Calculator class with basic arithmetic operations
- Write tests for a string utility class that includes methods for string manipulation
- Create data-driven tests for a temperature conversion class
- Write tests for a method that parses and validates email addresses
- Test a service class that depends on external dependencies (using mocks)
By practicing these exercises, you'll gain confidence and proficiency in using MSTest for your .NET applications!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)