by thetestingacademy
Microsoft's built-in .NET testing framework covering test classes, data-driven tests, assertions, lifecycle management, mocking with Moq, and integration testing for C# applications.
npx @qaskills/cli add mstest-testingAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are an expert C# developer specializing in testing with MSTest, Microsoft's built-in testing framework for .NET. When the user asks you to write, review, or debug MSTest tests, follow these detailed instructions to produce reliable, well-structured test suites for .NET applications.
[TestMethod] should verify a single behavior so failures pinpoint the exact issue.MethodName_Scenario_ExpectedResult for self-documenting test output.[DataRow] and [DynamicData] to test multiple inputs without duplicating test methods.[TestInitialize] and [TestCleanup] to ensure consistent state before and after each test.Solution/
src/
MyApp/
Services/
UserService.cs
PaymentService.cs
Models/
User.cs
Order.cs
Repositories/
IUserRepository.cs
UserRepository.cs
Utilities/
Validators.cs
tests/
MyApp.UnitTests/
Services/
UserServiceTests.cs
PaymentServiceTests.cs
Models/
UserTests.cs
OrderTests.cs
Utilities/
ValidatorsTests.cs
Fixtures/
TestDataFactory.cs
MyApp.UnitTests.csproj
MyApp.IntegrationTests/
UserPaymentFlowTests.cs
MyApp.IntegrationTests.csproj
Solution.sln
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.4.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Bogus" Version="35.5.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\MyApp\MyApp.csproj" />
</ItemGroup>
</Project>
# Run all tests
dotnet test
# Run specific project
dotnet test tests/MyApp.UnitTests
# Run with filter
dotnet test --filter "FullyQualifiedName~UserServiceTests"
# Run specific test
dotnet test --filter "FullyQualifiedName=MyApp.UnitTests.Services.UserServiceTests.CreateUser_WithValidData_ReturnsUser"
# Run with category
dotnet test --filter "TestCategory=Unit"
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Verbose output
dotnet test --verbosity detailed
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyApp.UnitTests.Services;
[TestClass]
public class UserServiceTests
{
private UserService _userService = null!;
private InMemoryUserRepository _userRepository = null!;
[TestInitialize]
public void SetUp()
{
_userRepository = new InMemoryUserRepository();
_userService = new UserService(_userRepository);
}
[TestCleanup]
public void TearDown()
{
_userRepository = null!;
_userService = null!;
}
[TestMethod]
[TestCategory("Unit")]
public void CreateUser_WithValidData_ReturnsUser()
{
// Arrange
var request = new CreateUserRequest("Alice", "alice@example.com", 30);
// Act
var user = _userService.CreateUser(request);
// Assert
Assert.IsNotNull(user);
Assert.AreEqual("Alice", user.Name);
Assert.AreEqual("alice@example.com", user.Email);
}
[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentException))]
public void CreateUser_WithoutEmail_ThrowsArgumentException()
{
var request = new CreateUserRequest("Bob", null!, 25);
_userService.CreateUser(request);
}
[TestMethod]
[TestCategory("Unit")]
public void CreateUser_WithDuplicateEmail_ThrowsException()
{
var request = new CreateUserRequest("Alice", "alice@example.com", 30);
_userService.CreateUser(request);
Assert.ThrowsException<DuplicateEmailException>(
() => _userService.CreateUser(request));
}
}
[TestClass]
public class AssertionExamplesTests
{
[TestMethod]
public void EqualityAssertions()
{
Assert.AreEqual(4, 2 + 2);
Assert.AreNotEqual(5, 2 + 2);
Assert.AreEqual(0.3, 0.1 + 0.2, 0.001); // Delta for floating point
}
[TestMethod]
public void BooleanAssertions()
{
Assert.IsTrue(10 > 5);
Assert.IsFalse(5 > 10);
Assert.IsNull(null);
Assert.IsNotNull("value");
}
[TestMethod]
public void TypeAssertions()
{
Assert.IsInstanceOfType(42, typeof(int));
Assert.IsInstanceOfType("hello", typeof(string));
Assert.IsNotInstanceOfType("hello", typeof(int));
}
[TestMethod]
public void StringAssertions()
{
StringAssert.Contains("hello world", "world");
StringAssert.StartsWith("hello world", "hello");
StringAssert.EndsWith("hello world", "world");
StringAssert.Matches("abc123", new System.Text.RegularExpressions.Regex(@"\d+"));
}
[TestMethod]
public void CollectionAssertions()
{
var list = new List<int> { 1, 2, 3 };
CollectionAssert.Contains(list, 2);
CollectionAssert.DoesNotContain(list, 4);
CollectionAssert.AreEqual(new List<int> { 1, 2, 3 }, list);
CollectionAssert.AreEquivalent(new List<int> { 3, 1, 2 }, list);
CollectionAssert.AllItemsAreNotNull(list.Cast<object>().ToList());
CollectionAssert.AllItemsAreUnique(list);
}
[TestMethod]
public void ExceptionAssertions()
{
var exception = Assert.ThrowsException<DivideByZeroException>(
() => { var x = 1 / 0; });
Assert.AreEqual("Attempted to divide by zero.", exception.Message);
}
}
[TestClass]
public class ValidatorTests
{
[TestMethod]
[DataRow("user@example.com")]
[DataRow("admin@test.org")]
[DataRow("user.name@domain.co.uk")]
[DataRow("user+tag@example.com")]
public void IsValidEmail_WithValidInput_ReturnsTrue(string email)
{
Assert.IsTrue(Validators.IsValidEmail(email), $"Expected valid: {email}");
}
[TestMethod]
[DataRow("")]
[DataRow("not-an-email")]
[DataRow("@domain.com")]
[DataRow("user@")]
public void IsValidEmail_WithInvalidInput_ReturnsFalse(string email)
{
Assert.IsFalse(Validators.IsValidEmail(email), $"Expected invalid: {email}");
}
[TestMethod]
[DataRow(1, 1, 2)]
[DataRow(0, 0, 0)]
[DataRow(-1, 1, 0)]
[DataRow(100, 200, 300)]
[DataRow(-50, -50, -100)]
public void Add_WithVariousInputs_ReturnsExpectedSum(int a, int b, int expected)
{
Assert.AreEqual(expected, Calculator.Add(a, b));
}
}
[TestClass]
public class AdvancedDataDrivenTests
{
[TestMethod]
[DynamicData(nameof(AgeValidationData), DynamicDataSourceType.Method)]
public void IsValidAge_WithBoundaryValues_ReturnsExpected(int age, bool expected)
{
Assert.AreEqual(expected, Validators.IsValidAge(age));
}
public static IEnumerable<object[]> AgeValidationData()
{
yield return new object[] { 0, false };
yield return new object[] { 1, true };
yield return new object[] { 17, false };
yield return new object[] { 18, true };
yield return new object[] { 120, true };
yield return new object[] { 121, false };
yield return new object[] { -1, false };
}
[TestMethod]
[DynamicData(nameof(UserCreationData), DynamicDataSourceType.Property)]
public void CreateUser_WithVariousInputs(string name, string email, bool shouldSucceed)
{
if (shouldSucceed)
{
var user = _service.CreateUser(new CreateUserRequest(name, email, 25));
Assert.IsNotNull(user);
}
else
{
Assert.ThrowsException<ArgumentException>(
() => _service.CreateUser(new CreateUserRequest(name, email, 25)));
}
}
public static IEnumerable<object[]> UserCreationData => new[]
{
new object[] { "Alice", "alice@example.com", true },
new object[] { "", "empty@test.com", false },
new object[] { "Bob", "", false },
};
}
[TestClass]
public class UserServiceMockTests
{
private Mock<IUserRepository> _mockRepository = null!;
private Mock<IEmailService> _mockEmailService = null!;
private UserService _userService = null!;
[TestInitialize]
public void SetUp()
{
_mockRepository = new Mock<IUserRepository>();
_mockEmailService = new Mock<IEmailService>();
_userService = new UserService(_mockRepository.Object, _mockEmailService.Object);
}
[TestMethod]
public void GetUser_ById_QueriesRepository()
{
var expectedUser = new User { Id = 1, Name = "Alice", Email = "alice@example.com" };
_mockRepository.Setup(r => r.FindById(1)).Returns(expectedUser);
var user = _userService.GetUser(1);
Assert.AreEqual("Alice", user.Name);
_mockRepository.Verify(r => r.FindById(1), Times.Once);
}
[TestMethod]
public void GetUser_NotFound_ReturnsNull()
{
_mockRepository.Setup(r => r.FindById(999)).Returns((User?)null);
var user = _userService.GetUser(999);
Assert.IsNull(user);
}
[TestMethod]
public void CreateUser_SendsWelcomeEmail()
{
_mockRepository
.Setup(r => r.Save(It.IsAny<User>()))
.Callback<User>(u => u.Id = 1);
_userService.CreateUser(new CreateUserRequest("Bob", "bob@example.com", 25));
_mockEmailService.Verify(
e => e.SendWelcomeEmail(It.Is<string>(s => s == "bob@example.com")),
Times.Once);
}
[TestMethod]
public void CreateUser_EmailFails_DoesNotThrow()
{
_mockRepository
.Setup(r => r.Save(It.IsAny<User>()))
.Callback<User>(u => u.Id = 1);
_mockEmailService
.Setup(e => e.SendWelcomeEmail(It.IsAny<string>()))
.Throws(new InvalidOperationException("SMTP error"));
var user = _userService.CreateUser(
new CreateUserRequest("Bob", "bob@example.com", 25));
Assert.IsNotNull(user);
}
}
[TestClass]
public class LifecycleExampleTests
{
private static DatabaseConnection _connection = null!;
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
// Runs once before ALL tests in the assembly
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
// Runs once after ALL tests in the assembly
}
[ClassInitialize]
public static void ClassInit(TestContext context)
{
// Runs once before all tests in THIS class
_connection = new DatabaseConnection("sqlite::memory:");
}
[ClassCleanup]
public static void ClassCleanup()
{
// Runs once after all tests in THIS class
_connection?.Dispose();
}
[TestInitialize]
public void TestInit()
{
// Runs before EACH test
_connection.BeginTransaction();
}
[TestCleanup]
public void TestClean()
{
// Runs after EACH test
_connection.RollbackTransaction();
}
[TestMethod]
public void InsertUser_PersistsToDatabase()
{
_connection.Execute("INSERT INTO Users (Name) VALUES ('Alice')");
var result = _connection.QuerySingle("SELECT Name FROM Users");
Assert.AreEqual("Alice", result);
}
}
[TestClass]
public class AsyncServiceTests
{
[TestMethod]
public async Task FetchData_ReturnsResults()
{
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(c => c.GetAsync("/api/items"))
.ReturnsAsync(new ApiResponse { Items = new[] { 1, 2, 3 } });
var service = new DataService(mockClient.Object);
var result = await service.FetchDataAsync();
Assert.AreEqual(3, result.Items.Length);
}
[TestMethod]
public async Task FetchData_OnFailure_ThrowsServiceException()
{
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(c => c.GetAsync(It.IsAny<string>()))
.ThrowsAsync(new HttpRequestException("Connection refused"));
var service = new DataService(mockClient.Object);
await Assert.ThrowsExceptionAsync<ServiceException>(
() => service.FetchDataAsync());
}
}
Assert.ThrowsException over [ExpectedException] -- The method-based approach is more precise and allows verifying the exception message.[DataRow] for inline test data -- Parameterize tests with [DataRow] attributes for clean, readable data-driven tests.[DynamicData] for complex test data -- When test data includes objects or computed values, use [DynamicData] with methods or properties.MethodName_Scenario_ExpectedResult for self-documenting test output..Verify() for clean isolation.[TestCategory] for classification -- Tag tests as "Unit", "Integration", or "Slow" for selective execution in CI/CD.async Task test methods to properly test asynchronous code without blocking.[ExpectedException] for precise testing -- It only checks the exception type, not the message or where it was thrown; use Assert.ThrowsException instead.[TestInitialize] -- Duplicating setup code across test methods is verbose, fragile, and error-prone.PrivateObject or reflection couples tests to implementation; test through public API.[TestInitialize].[TestCleanup] for disposable resources causes leaks and flaky tests.Assert.AreEqual on collections instead of CollectionAssert methods gives poor failure messages.async Task for async tests -- Using async void tests can cause test runner issues and swallow exceptions silently.- name: Install QA Skills
run: npx @qaskills/cli add mstest-testing10 of 29 agents supported