MSTest
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");
});
}
}