// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Moq; using TestFramework.ForTestingMSTest; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public class TestMethodValidatorTests : TestContainer { private readonly TestMethodValidator _testMethodValidator; private readonly Mock _mockReflectHelper; private readonly List _warnings; private readonly Mock _mockMethodInfo; private readonly Type _type; public TestMethodValidatorTests() { _mockReflectHelper = new Mock(MockBehavior.Strict); _testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, discoverInternals: false); _warnings = []; _mockMethodInfo = new Mock(MockBehavior.Strict); _type = typeof(TestMethodValidatorTests); } public void IsValidTestMethodShouldReturnFalseForMethodsWithoutATestMethodAttributeOrItsDerivedAttributes() { var declaringType = new Mock(MockBehavior.Strict); _mockMethodInfo.Setup(x => x.DeclaringType).Returns(declaringType.Object); _mockReflectHelper.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Returns(false); _testMethodValidator.IsValidTestMethod(_mockMethodInfo.Object, _type, _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnTrueForGenericTestMethodDefinitions() { SetupTestMethod(); _mockReflectHelper .Setup(x => x.GetFirstAttributeOrDefault(_mockMethodInfo.Object)) .Returns(default(AsyncStateMachineAttribute?)); _mockMethodInfo.Setup(mi => mi.IsGenericMethodDefinition).Returns(true); _mockMethodInfo.Setup(mi => mi.DeclaringType!.FullName).Returns("DummyTestClass"); _mockMethodInfo.Setup(mi => mi.Name).Returns("DummyTestMethod"); _mockMethodInfo.Setup(mi => mi.Attributes).Returns(MethodAttributes.Public); _mockMethodInfo.Setup(mi => mi.ReturnType).Returns(typeof(void)); _mockMethodInfo.Setup(mi => mi.GetParameters()).Returns([]); _testMethodValidator.IsValidTestMethod(_mockMethodInfo.Object, _type, _warnings).Should().BeTrue(); _warnings.Count.Should().Be(0); } public void IsValidTestMethodShouldReturnFalseForNonPublicMethods() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "InternalTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)!; _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnFalseForAbstractMethods() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "AbstractTestMethod", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnFalseForStaticMethods() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "StaticTestMethod", BindingFlags.Static | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnFalseForInstanceMethodsOnFSharpModules() { SetupTestMethod(); Type moduleType = FSharpModuleTestFixture.ModuleType; MethodInfo methodInfo = moduleType.GetMethod("InstanceMethod")!; _mockReflectHelper .Setup(x => x.GetFirstAttributeOrDefault(methodInfo)) .Returns(default(AsyncStateMachineAttribute?)); moduleType.IsFSharpModule().Should().BeTrue(); _testMethodValidator.IsValidTestMethod(methodInfo, moduleType, _warnings).Should().BeFalse(); _warnings.Should().ContainSingle(); _warnings[0].Should().Be(string.Format( CultureInfo.CurrentCulture, Resource.UTA_ErrorIncorrectFSharpTestMethodSignature, moduleType.FullName, methodInfo.Name)); } public void IsValidTestMethodShouldReturnTrueForStaticMethodsOnFSharpModules() { SetupTestMethod(); Type moduleType = FSharpModuleTestFixture.ModuleType; MethodInfo methodInfo = moduleType.GetMethod("StaticMethod")!; _mockReflectHelper .Setup(x => x.GetFirstAttributeOrDefault(methodInfo)) .Returns(default(AsyncStateMachineAttribute?)); moduleType.IsFSharpModule().Should().BeTrue(); _testMethodValidator.IsValidTestMethod(methodInfo, moduleType, _warnings).Should().BeTrue(); _warnings.Should().BeEmpty(); } public void IsValidTestMethodShouldReportFSharpSignatureWarningForInstanceGenericValueTaskMethodsOnModules() { SetupTestMethod(); Type moduleType = FSharpModuleTestFixture.ModuleType; MethodInfo methodInfo = moduleType.GetMethod("InstanceGenericValueTaskMethod")!; moduleType.IsFSharpModule().Should().BeTrue(); // Changing only the return type cannot fix an instance method on an F# module. _testMethodValidator.IsValidTestMethod(methodInfo, moduleType, _warnings).Should().BeFalse(); _warnings.Should().ContainSingle(); _warnings[0].Should().Be(string.Format( CultureInfo.CurrentCulture, Resource.UTA_ErrorIncorrectFSharpTestMethodSignature, moduleType.FullName, methodInfo.Name)); _warnings[0].Should().NotContain("ValueTask"); } public void IsValidTestMethodShouldReturnFalseForGenericTestMethods() { SetupTestMethod(); Action action = () => new DummyTestClassWithGenericMethods().GenericMethod(); _testMethodValidator.IsValidTestMethod(action.Method, typeof(DummyTestClassWithGenericMethods), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnFalseForAsyncMethodsWithNonTaskReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "AsyncMethodWithVoidReturnType", BindingFlags.Instance | BindingFlags.Public)!; _mockReflectHelper.Setup(_mockReflectHelper => _mockReflectHelper.GetFirstAttributeOrDefault(methodInfo)) .CallBase(); _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnFalseForMethodsWithNonVoidReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "MethodWithIntReturnType", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } public void IsValidTestMethodShouldReturnTrueForAsyncMethodsWithTaskReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "AsyncMethodWithTaskReturnType", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, _type, _warnings).Should().BeTrue(); } public void IsValidTestMethodShouldReturnTrueForNonAsyncMethodsWithTaskReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "MethodWithTaskReturnType", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, _type, _warnings).Should().BeTrue(); } public void IsValidTestMethodShouldReturnTrueForMethodsWithValueTaskReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "MethodWithValueTaskReturnType", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, _type, _warnings).Should().BeTrue(); _warnings.Count.Should().Be(0); } public void IsValidTestMethodShouldReturnFalseAndReportTargetedWarningForGenericValueTaskReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "MethodWithGenericValueTaskReturnType", BindingFlags.Instance | BindingFlags.Public)!; _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); _warnings.Should().ContainSingle(); _warnings[0].Should().Contain("ValueTask"); } public void IsValidTestMethodShouldReportGenericSignatureWarningWhenGenericValueTaskMethodIsAlsoInvalidForAnotherReason() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "StaticMethodWithGenericValueTaskReturnType", BindingFlags.Static | BindingFlags.Public)!; // The method is invalid because it is static; the targeted ValueTask message must NOT be used here, // otherwise the user could "fix" the return type and still have an invalid (static) method. _testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); _warnings.Should().ContainSingle(); _warnings[0].Should().NotContain("ValueTask"); } public void IsValidTestMethodShouldReturnTrueForMethodsWithVoidReturnType() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "MethodWithVoidReturnType", BindingFlags.Instance | BindingFlags.Public)!; _mockReflectHelper .Setup(x => x.GetFirstAttributeOrDefault(methodInfo)) .Returns(default(AsyncStateMachineAttribute?)); _testMethodValidator.IsValidTestMethod(methodInfo, _type, _warnings).Should().BeTrue(); } #region Discovery of internals enabled public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnTrueForInternalMethods() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "InternalTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)!; _mockReflectHelper .Setup(x => x.GetFirstAttributeOrDefault(methodInfo)) .Returns(default(AsyncStateMachineAttribute?)); var testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, true); testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeTrue(); } public void WhenDiscoveryOfInternalsIsEnabledIsValidTestMethodShouldReturnFalseForPrivateMethods() { SetupTestMethod(); MethodInfo methodInfo = typeof(DummyTestClass).GetMethod( "PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)!; var testMethodValidator = new TestMethodValidator(_mockReflectHelper.Object, true); testMethodValidator.IsValidTestMethod(methodInfo, typeof(DummyTestClass), _warnings).Should().BeFalse(); } #endregion private void SetupTestMethod() => _mockReflectHelper.Setup( rh => rh.IsAttributeDefined(It.IsAny())).Returns(true); } #region Dummy types public class DummyTestClassWithGenericMethods { public void GenericMethod() { } } internal abstract class DummyTestClass { public static void StaticTestMethod() { } public abstract void AbstractTestMethod(); [SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Done on purpose")] public async void AsyncMethodWithVoidReturnType() => await Task.FromResult(true); public async Task AsyncMethodWithTaskReturnType() => await Task.Delay(TimeSpan.Zero); public Task MethodWithTaskReturnType() => Task.Delay(TimeSpan.Zero); public ValueTask MethodWithValueTaskReturnType() => default; public ValueTask MethodWithGenericValueTaskReturnType() => default; public static ValueTask StaticMethodWithGenericValueTaskReturnType() => default; public int MethodWithIntReturnType() => 0; public void MethodWithVoidReturnType() { } internal void InternalTestMethod() { } #pragma warning disable IDE0051 // Remove unused private members private void PrivateTestMethod() #pragma warning restore IDE0051 // Remove unused private members { } } #endregion