MSTest

TestingUnit TestingC#.NETMicrosoftVisual Studio

Testing Tool

MSTest

Overview

MSTest is Microsoft's official testing framework for .NET applications. It provides integration with Visual Studio and Visual Studio Code Test Explorer, .NET CLI, and numerous CI pipelines, supporting all .NET targets including .NET Framework, .NET Core, .NET, UWP, and WinUI. As a mature, open-source, and cross-platform testing framework, it's widely adopted in enterprise development environments.

Details

MSTest's greatest strength lies in its deep integration with Microsoft's development ecosystem. It offers exceptional test execution and debugging experience in Visual Studio, with excellent IntelliSense support and Test Explorer integration for efficient test development. MSTest 3.8 (2024) introduces integration with Microsoft.Testing.Platform (MTP), improved filtering capabilities, and cooperative cancellation features.

The attribute-based test definition provides rich attributes including [TestClass], [TestMethod], [DataRow], [TestInitialize], and [TestCleanup], enabling fine-grained control over test lifecycle. Parallel execution features allow concurrent test execution at class and method levels, achieving fast execution even for large test suites.

Pros and Cons

Pros

  • Visual Studio Integration: Best-in-class IDE integration and debugging experience
  • Microsoft Official: Stability and long-term support within .NET ecosystem
  • Rich Attributes: Comprehensive attribute set for test lifecycle control
  • Data-Driven Testing: Flexible parameterization with DataRow and DynamicData
  • Parallel Execution: Support for concurrent execution at class and method levels
  • Enterprise Ready: Proven track record and reliability in large-scale projects

Cons

  • Microsoft Dependency: Cannot be used outside .NET environments
  • Configuration Complexity: Complex setup when using advanced features
  • Limited Flexibility: Less customizable compared to other frameworks
  • Learning Curve: Need to learn Microsoft-specific concepts and naming conventions

Reference Pages

Code Examples

Hello World

// MSTest.Sdk project file
<Project Sdk="MSTest.Sdk/3.8.3">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>

// HelloWorldTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class HelloWorldTest
{
    [TestMethod]
    public void TestGreeting()
    {
        string result = Greet("MSTest");
        Assert.AreEqual("Hello, MSTest!", result);
    }
    
    private string Greet(string name)
    {
        return $"Hello, {name}!";
    }
}

Basic Testing

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class CalculatorTest
{
    private Calculator _calculator;
    
    [TestInitialize]
    public void Setup()
    {
        _calculator = new Calculator();
    }
    
    [TestMethod]
    public void Add_PositiveNumbers_ReturnsCorrectSum()
    {
        // Arrange
        int a = 5;
        int b = 3;
        
        // Act
        int result = _calculator.Add(a, b);
        
        // Assert
        Assert.AreEqual(8, result);
    }
    
    [TestMethod]
    public void Add_NegativeNumbers_ReturnsCorrectSum()
    {
        // Arrange & Act
        int result = _calculator.Add(-2, -3);
        
        // Assert
        Assert.AreEqual(-5, result);
    }
    
    [TestMethod]
    [ExpectedException(typeof(DivideByZeroException))]
    public void Divide_ByZero_ThrowsException()
    {
        // Act
        _calculator.Divide(10, 0);
    }
    
    [TestMethod]
    [Timeout(2000)]
    public void ComplexCalculation_CompletesWithinTimeout()
    {
        // Act
        var result = _calculator.ComplexCalculation();
        
        // Assert
        Assert.IsNotNull(result);
    }
    
    [TestCleanup]
    public void Cleanup()
    {
        _calculator?.Dispose();
    }
}

Mocking

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
public class UserServiceTest
{
    private Mock<IUserRepository> _mockRepository;
    private UserService _userService;
    
    [TestInitialize]
    public void Setup()
    {
        _mockRepository = new Mock<IUserRepository>();
        _userService = new UserService(_mockRepository.Object);
    }
    
    [TestMethod]
    public void GetUser_ValidId_ReturnsUser()
    {
        // Arrange
        var expectedUser = new User { Id = 1, Name = "John Doe" };
        _mockRepository.Setup(r => r.GetById(1))
                      .Returns(expectedUser);
        
        // Act
        var result = _userService.GetUser(1);
        
        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual("John Doe", result.Name);
        _mockRepository.Verify(r => r.GetById(1), Times.Once);
    }
    
    [TestMethod]
    public void CreateUser_ValidUser_CallsRepository()
    {
        // Arrange
        var newUser = new User { Name = "Jane Smith" };
        var savedUser = new User { Id = 2, Name = "Jane Smith" };
        
        _mockRepository.Setup(r => r.Save(It.IsAny<User>()))
                      .Returns(savedUser);
        
        // Act
        var result = _userService.CreateUser(newUser);
        
        // Assert
        Assert.AreEqual(2, result.Id);
        Assert.AreEqual("Jane Smith", result.Name);
        _mockRepository.Verify(r => r.Save(It.IsAny<User>()), Times.Once);
    }
}

Async Testing

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;

[TestClass]
public class AsyncOperationTest
{
    [TestMethod]
    public async Task ProcessDataAsync_ValidInput_ReturnsProcessedData()
    {
        // Arrange
        var processor = new DataProcessor();
        var inputData = "test data";
        
        // Act
        var result = await processor.ProcessDataAsync(inputData);
        
        // Assert
        Assert.IsNotNull(result);
        Assert.IsTrue(result.Contains("processed"));
    }
    
    [TestMethod]
    public async Task DownloadFileAsync_LargeFile_CompletesSuccessfully()
    {
        // Arrange
        var downloader = new FileDownloader();
        var url = "https://example.com/largefile.zip";
        
        // Act
        var success = await downloader.DownloadAsync(url);
        
        // Assert
        Assert.IsTrue(success);
    }
    
    [TestMethod]
    [Timeout(5000)]
    public async Task SlowOperationAsync_WithTimeout_CompletesWithinTimeLimit()
    {
        // Arrange
        var service = new SlowService();
        
        // Act
        var result = await service.ProcessSlowlyAsync();
        
        // Assert
        Assert.IsNotNull(result);
    }
}

Setup and Teardown

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class DatabaseIntegrationTest
{
    private static DatabaseContext _context;
    private TestDataBuilder _dataBuilder;
    
    [ClassInitialize]
    public static void ClassSetup(TestContext context)
    {
        // Initialize for entire test class
        _context = new DatabaseContext("test-connection-string");
        _context.Database.EnsureCreated();
    }
    
    [ClassCleanup]
    public static void ClassTeardown()
    {
        // Cleanup for entire test class
        _context?.Database.EnsureDeleted();
        _context?.Dispose();
    }
    
    [TestInitialize]
    public void Setup()
    {
        // Setup before each test method
        _dataBuilder = new TestDataBuilder(_context);
        _dataBuilder.SeedTestData();
    }
    
    [TestCleanup]
    public void Cleanup()
    {
        // Cleanup after each test method
        _dataBuilder?.CleanTestData();
    }
    
    [TestMethod]
    public void SaveUser_ValidUser_SavesSuccessfully()
    {
        // Arrange
        var user = new User { Name = "Test User", Email = "[email protected]" };
        
        // Act
        _context.Users.Add(user);
        _context.SaveChanges();
        
        // Assert
        var savedUser = _context.Users.FirstOrDefault(u => u.Email == "[email protected]");
        Assert.IsNotNull(savedUser);
        Assert.AreEqual("Test User", savedUser.Name);
    }
    
    [TestMethod]
    public void GetUsers_WithData_ReturnsAllUsers()
    {
        // Act
        var users = _context.Users.ToList();
        
        // Assert
        Assert.IsTrue(users.Count > 0);
    }
}

Advanced Features

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class AdvancedMSTestFeatures
{
    // Data-driven testing
    [TestMethod]
    [DataRow("apple", 5)]
    [DataRow("banana", 6)]
    [DataRow("cherry", 6)]
    public void TestStringLength(string input, int expectedLength)
    {
        Assert.AreEqual(expectedLength, input.Length);
    }
    
    // Dynamic data provider
    [TestMethod]
    [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)]
    public void TestWithDynamicData(int value, int expected)
    {
        var result = Math.Abs(value);
        Assert.AreEqual(expected, result);
    }
    
    public static IEnumerable<object[]> GetTestData()
    {
        yield return new object[] { -5, 5 };
        yield return new object[] { 10, 10 };
        yield return new object[] { -15, 15 };
    }
    
    // Conditional testing
    [TestMethod]
    public void TestOnlyOnWindows()
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            Assert.Inconclusive("This test runs only on Windows");
        }
        
        // Windows-specific test logic
        Assert.IsTrue(true);
    }
    
    // Categorized testing
    [TestMethod]
    [TestCategory("Integration")]
    [TestCategory("Database")]
    public void DatabaseIntegrationTest()
    {
        // Integration test logic
        Assert.IsTrue(true);
    }
    
    [TestMethod]
    [TestCategory("Unit")]
    public void UnitTest()
    {
        // Unit test logic
        Assert.IsTrue(true);
    }
    
    // Custom attributes
    [TestMethod]
    [Owner("Development Team")]
    [Priority(1)]
    [Description("Test for critical functionality")]
    public void CriticalFeatureTest()
    {
        // Critical feature test
        Assert.IsTrue(true);
    }
    
    // Parallel execution tests
    [TestMethod]
    public void ParallelTest1()
    {
        Thread.Sleep(1000);
        Assert.IsTrue(true);
    }
    
    [TestMethod]
    public void ParallelTest2()
    {
        Thread.Sleep(1000);
        Assert.IsTrue(true);
    }
    
    // Various assertions
    [TestMethod]
    public void TestVariousAssertions()
    {
        // Basic assertions
        Assert.IsTrue(true);
        Assert.IsFalse(false);
        Assert.IsNull(null);
        Assert.IsNotNull("value");
        
        // String assertions
        StringAssert.Contains("Hello World", "World");
        StringAssert.StartsWith("Hello World", "Hello");
        StringAssert.EndsWith("Hello World", "World");
        
        // Collection assertions
        var list = new List<int> { 1, 2, 3 };
        CollectionAssert.Contains(list, 2);
        CollectionAssert.DoesNotContain(list, 5);
        
        // Exception assertions
        Assert.ThrowsException<ArgumentException>(() => {
            throw new ArgumentException("test");
        });
    }
}