seed-skills/mstest-testing/SKILL.md
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 skillsauth add PramodDutta/qaskills MSTest TestingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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", "[email protected]", 30);
// Act
var user = _userService.CreateUser(request);
// Assert
Assert.IsNotNull(user);
Assert.AreEqual("Alice", user.Name);
Assert.AreEqual("[email protected]", 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", "[email protected]", 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("[email protected]")]
[DataRow("[email protected]")]
[DataRow("[email protected]")]
[DataRow("[email protected]")]
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", "[email protected]", true },
new object[] { "", "[email protected]", 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 = "[email protected]" };
_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", "[email protected]", 25));
_mockEmailService.Verify(
e => e.SendWelcomeEmail(It.Is<string>(s => s == "[email protected]")),
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", "[email protected]", 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.development
Generate test data from the schemas you already have. Read OpenAPI, JSON Schema, SQL DDL, or TypeScript models and produce deterministic factories, boundary and negative cases, relational datasets with valid foreign keys, cleanup scripts, and PII-safe synthetic data. Production records never leave the machine.
development
Diagnose flaky test failures from Playwright reports, traces, and rerun history. Classify each failure as product, test, environment, data, or unknown with cited evidence and a proposed fix. Never auto-modifies code without opt-in.
tools
Test LLM, RAG, MCP, and agentic systems end to end. Build golden datasets, run deterministic checks and LLM judges, score retrieval, probe prompt injection, verify tool use, and gate CI on thresholds. Orchestrates DeepEval, Ragas, promptfoo, and Langfuse.
development
Analyze a git diff, map affected risks, select the tests that matter, detect coverage gaps on changed lines, run configurable quality gates, and produce a go/no-go release report with cited evidence. Recommends only; never merges or deploys.