From 9a3e80193a88be06e9decad94d23cd18f987a865 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:22:54 -0700 Subject: [PATCH] Refactor unit tests: data driven tests and leverage Microsoft.CodeAnalysis.Testing patterns (#76) After the switch to using `Microsoft.CodeAnalysis.Testing`, refactor the tests to better follow the test infrastructure conventions. (For additional examples, check out the dotnet/roslyn-analyzers repo). 1. Use helper classes instead of inheritance to make the tests more flexible 2. Centralize test defaults in a new `Test` class rather than in a base class 3. Add a set of global usings to the default test to simplify tests and make test authoring easier 4. Use xUnit's data-driven test method to simplify test scenarios - Allows for a matrix of tests, both in the global namespace (fixes #56) and in a given namespace - Makes test debugging a bit easier as there's only a single Moq call asserted in each test - Makes reasoning about the test a bit more difficult, as now there's distance between the test clause and the setup 5. After the test refactoring, there were a few cases of clear duplication; removed and verified code coverage numbers are the same I tried to also create "doppelganger" tests in this PR, like in `NoConstructorArgumentsForInterfaceMockAnalyzerTests` where a user creates a `Mock` class in the namespace to make sure the analyzers are doing fully-qualified name resolution. However, to do that generically requires duplicating a lot of more of Moq's own API, and I'm not sure if that's a good idea, so filed #75 to track. There's probably more that can be done to DRY out the test templates, but I wanted to make sure this was a good step forward before going any further. --- .../Moq.Analyzers.Test/AbstractClassTests.cs | 220 ---------- .../AsAcceptOnlyInterfaceAnalyzerTests.cs | 118 +---- ...tureShouldMatchMockedMethodCodeFixTests.cs | 411 ++++++------------ ...ructorArgumentsShouldMatchAnalyzerTests.cs | 158 +++---- .../Helpers/AnalyzerTestExtensions.cs | 15 - .../Helpers/AnalyzerVerifier.cs | 18 + .../Helpers/CodeFixVerifier.cs | 14 +- .../Helpers/DiagnosticVerifier.cs | 21 - Source/Moq.Analyzers.Test/Helpers/Test.cs | 33 ++ ...rArgumentsForInterfaceMockAnalyzerTests.cs | 101 ++--- .../NoMethodsInPropertySetupAnalyzerTests.cs | 68 +-- .../NoSealedClassMocksAnalyzerTests.cs | 53 +-- ...dOnlyForOverridableMembersAnalyzerTests.cs | 227 ++-------- ...houldNotIncludeAsyncResultAnalyzerTests.cs | 86 +--- 14 files changed, 390 insertions(+), 1153 deletions(-) delete mode 100644 Source/Moq.Analyzers.Test/AbstractClassTests.cs delete mode 100644 Source/Moq.Analyzers.Test/Helpers/AnalyzerTestExtensions.cs create mode 100644 Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs delete mode 100644 Source/Moq.Analyzers.Test/Helpers/DiagnosticVerifier.cs create mode 100644 Source/Moq.Analyzers.Test/Helpers/Test.cs diff --git a/Source/Moq.Analyzers.Test/AbstractClassTests.cs b/Source/Moq.Analyzers.Test/AbstractClassTests.cs deleted file mode 100644 index 80ff9549..00000000 --- a/Source/Moq.Analyzers.Test/AbstractClassTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -namespace Moq.Analyzers.Test; - -public class AbstractClassTests : DiagnosticVerifier -{ - // TODO: Review use of `.As<>()` in the test cases. It is not clear what purpose it serves. - [Fact] - public async Task ShouldFailOnGenericTypesWithMismatchArgs() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.Data.AbstractClass.GenericMistmatchArgs; - - internal abstract class AbstractGenericClassDefaultCtor - { - protected AbstractGenericClassDefaultCtor() - { - } - } - - internal abstract class AbstractGenericClassWithCtor - { - protected AbstractGenericClassWithCtor(int a) - { - } - - protected AbstractGenericClassWithCtor(int a, string b) - { - } - } - - internal class MyUnitTests - { - private void TestBadWithGeneric() - { - // The class has a constructor that takes an Int32 but passes a String - var mock = new Mock>{|Moq1002:("42")|}; - - // The class has a ctor with two arguments [Int32, String], but they are passed in reverse order - var mock1 = new Mock>{|Moq1002:("42", 42)|}; - - // The class has a ctor but does not take any arguments - var mock2 = new Mock>{|Moq1002:(42)|}; - } - } - """); - } - - [Fact] - public async Task ShouldPassOnGenericTypesWithNoArgs() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.Data.AbstractClass.GenericNoArgs; - - internal abstract class AbstractGenericClassDefaultCtor - { - protected AbstractGenericClassDefaultCtor() - { - } - } - - internal class MyUnitTests - { - private void TestForBaseGenericNoArgs() - { - var mock = new Mock>(); - mock.As>(); - - var mock1 = new Mock>(); - - var mock2 = new Mock>(MockBehavior.Default); - } - } - """); - } - - [Fact] - public async Task ShouldFailOnMismatchArgs() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.Data.AbstractClass.MismatchArgs; - - internal abstract class AbstractClassDefaultCtor - { - protected AbstractClassDefaultCtor() - { - } - } - - internal abstract class AbstractClassWithCtor - { - protected AbstractClassWithCtor(int a) - { - } - - protected AbstractClassWithCtor(int a, string b) - { - } - } - - internal class MyUnitTests - { - private void TestBad() - { - // The class has a ctor that takes an Int32 but passes a String - var mock = new Mock{|Moq1002:("42")|}; - - // The class has a ctor with two arguments [Int32, String], but they are passed in reverse order - var mock1 = new Mock{|Moq1002:("42", 42)|}; - - // The class has a ctor but does not take any arguments - var mock2 = new Mock{|Moq1002:(42)|}; - } - } - """); - } - - [Fact] - public async Task ShouldPassWithNoArgs() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.Data.AbstractClass.NoArgs; - - internal abstract class AbstractClassDefaultCtor - { - protected AbstractClassDefaultCtor() - { - } - } - - internal class MyUnitTests - { - // Base case that we can handle abstract types - private void TestForBaseNoArgs() - { - var mock = new Mock(); - mock.As(); - } - } - """); - } - - [Fact(Skip = "I think this _should_ fail, but currently passes. Tracked by #55.")] - public async Task ShouldFailWithArgsNonePassed() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.Data.AbstractClass.WithArgsNonePassed; - - internal abstract class AbstractClassWithCtor - { - protected AbstractClassWithCtor(int a) - { - } - - protected AbstractClassWithCtor(int a, string b) - { - } - } - - internal class MyUnitTests - { - // This is syntatically not allowed by C#, but you can do it with Moq - private void TestForBaseWithArgsNonePassed() - { - var mock = new Mock(); - mock.As(); - } - } - """); - } - - [Fact] - public async Task ShouldPassWithArgsPassed() - { - await VerifyCSharpDiagnostic( - """ - namespace Moq.Analyzers.Test.DataAbstractClass.WithArgsPassed; - - internal abstract class AbstractGenericClassWithCtor - { - protected AbstractGenericClassWithCtor(int a) - { - } - - protected AbstractGenericClassWithCtor(int a, string b) - { - } - } - - internal abstract class AbstractClassWithCtor - { - protected AbstractClassWithCtor(int a) - { - } - - protected AbstractClassWithCtor(int a, string b) - { - } - } - - internal class MyUnitTests - { - private void TestForBaseWithArgsPassed() - { - var mock = new Mock(42); - var mock2 = new Mock(MockBehavior.Default, 42); - - var mock3 = new Mock(42, "42"); - var mock4 = new Mock(MockBehavior.Default, 42, "42"); - - var mock5 = new Mock>(42); - var mock6 = new Mock>(MockBehavior.Default, 42); - } - } - """); - } -} diff --git a/Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs b/Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs index 47bbeed4..e41fac67 100644 --- a/Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs @@ -1,40 +1,29 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class AsAcceptOnlyInterfaceAnalyzerTests : DiagnosticVerifier +public class AsAcceptOnlyInterfaceAnalyzerTests { - [Fact] - public async Task ShouldFailWhenUsingAsWithAbstractClass() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace AsAcceptOnlyInterface.TestBadAsForAbstractClass; - - public abstract class BaseSampleClass - { - public int Calculate() => 0; - } - - internal class MyUnitTests - { - private void TestBadAsForAbstractClass() - { - var mock = new Mock(); - mock.As<{|Moq1300:BaseSampleClass|}>(); - } - } - """); + foreach (var @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + // TODO: .As and .As feels redundant + yield return [@namespace, """new Mock().As<{|Moq1300:BaseSampleClass|}>();"""]; + yield return [@namespace, """new Mock().As<{|Moq1300:SampleClass|}>();"""]; + yield return [@namespace, """new Mock().As();"""]; + // TODO: Testing with .Setup() and .Returns() seems unnecessary. + yield return [@namespace, """new Mock().As().Setup(x => x.Calculate(It.IsAny(), It.IsAny())).Returns(10);"""]; + } } - [Fact] - public async Task ShouldFailWhenUsingAsWithConcreteClass() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldAnalyzeAs(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace AsAcceptOnlyInterface.TestBadAsForNonAbstractClass; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} public interface ISampleInterface { @@ -46,80 +35,17 @@ public abstract class BaseSampleClass public int Calculate() => 0; } - public class OtherClass - { - - public int Calculate() => 0; - } - - internal class MyUnitTests - { - private void TestBadAsForNonAbstractClass() - { - var mock = new Mock(); - mock.As<{|Moq1300:OtherClass|}>(); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenUsingAsWithInterface() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace AsAcceptOnlyInterface.TestOkAsForInterface; - - public interface ISampleInterface - { - int Calculate(int a, int b); - } - public class SampleClass { - public int Calculate() => 0; - } - - internal class MyUnitTests - { - private void TestOkAsForInterface() - { - var mock = new Mock(); - mock.As(); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenUsingAsWithInterfaceWithSetup() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace AsAcceptOnlyInterface.TestOkAsForInterfaceWithConfiguration; - - public interface ISampleInterface - { - int Calculate(int a, int b); - } - public class SampleClass - { public int Calculate() => 0; } - internal class MyUnitTests + internal class UnitTest { - private void TestOkAsForInterfaceWithConfiguration() + private void Test() { - var mock = new Mock(); - mock.As() - .Setup(x => x.Calculate(It.IsAny(), It.IsAny())) - .Returns(10); + {{mock}} } } """); diff --git a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs b/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs index 6ce11b6e..80e1292a 100644 --- a/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs +++ b/Source/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodCodeFixTests.cs @@ -1,77 +1,134 @@ +using Verifier = Moq.Analyzers.Test.Helpers.CodeFixVerifier; + namespace Moq.Analyzers.Test; -public class CallbackSignatureShouldMatchMockedMethodCodeFixTests : CodeFixVerifier +public class CallbackSignatureShouldMatchMockedMethodCodeFixTests { - [Fact] - public async Task ShouldPassWhenCorrectSetupAndReturns() + public static IEnumerable TestData() { - await VerifyCSharpFix( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.MyGoodSetupAndReturns; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void MyGoodSetupAndReturns() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Returns((string s) => { return 0; }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns((int i, string s, DateTime dt) => { return 0; }); - mock.Setup(x => x.Do(It.IsAny>())).Returns((List l) => { return 0; }); - } - } - """, - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.MyGoodSetupAndReturns; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void MyGoodSetupAndReturns() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Returns((string s) => { return 0; }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns((int i, string s, DateTime dt) => { return 0; }); - mock.Setup(x => x.Do(It.IsAny>())).Returns((List l) => { return 0; }); - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Returns((string s) => { return 0; });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Returns((string s) => { return 0; });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns((int i, string s, DateTime dt) => { return 0; });""", + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns((int i, string s, DateTime dt) => { return 0; });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny>())).Returns((List l) => { return 0; });""", + """new Mock().Setup(x => x.Do(It.IsAny>())).Returns((List l) => { return 0; });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Callback({|Moq1100:(int i)|} => { });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Callback((string s) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Callback({|Moq1100:(string s1, string s2)|} => { });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Callback((string s) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback({|Moq1100:(string s1, int i1)|} => { });""", + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback({|Moq1100:(int i)|} => { });""", + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback((List l) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Callback((string s) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Callback((string s) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback((List l) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback((List l) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Callback(() => { });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Callback(() => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback(() => { });""", + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback(() => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback(() => { });""", + """new Mock().Setup(x => x.Do(It.IsAny>())).Callback(() => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny())).Returns(0).Callback((string s) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny())).Returns(0).Callback((string s) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns(0).Callback((int i, string s, DateTime dt) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns(0).Callback((int i, string s, DateTime dt) => { });""", + ]; + + yield return + [ + @namespace, + """new Mock().Setup(x => x.Do(It.IsAny>())).Returns(0).Callback((List l) => { });""", + """new Mock().Setup(x => x.Do(It.IsAny>())).Returns(0).Callback((List l) => { });""", + ]; + } } - [Fact] - public async Task ShouldSuggestQuickFixWhenIncorrectCallbacks() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldSuggestQuickFixWhenIncorrectCallbacks(string @namespace, string original, string quickFix) { - await VerifyCSharpFix( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestBadCallbacks; + static string Template(string ns, string mock) => + $$""" + {{ns}} internal interface IFoo { @@ -82,225 +139,15 @@ internal interface IFoo int Do(List l); } - internal class MyUnitTests + internal class UnitTest { - private void TestBadCallbacks() + private void Test() { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback({|Moq1100:(int i)|} => { }); - mock.Setup(x => x.Do(It.IsAny())).Callback({|Moq1100:(string s1, string s2)|} => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback({|Moq1100:(string s1, int i1)|} => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback({|Moq1100:(int i)|} => { }); + {{mock}} } } - """, - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestBadCallbacks; - - internal interface IFoo - { - int Do(string s); + """; - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestBadCallbacks() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny())).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback((List l) => { }); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenCorrectSetupAndCallbacks() - { - await VerifyCSharpFix( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback((List l) => { }); - } - } - """, - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback((int i, string s, DateTime dt) => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback((List l) => { }); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenCorrectSetupAndParameterlessCallbacks() - { - await VerifyCSharpFix( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndParameterlessCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndParameterlessCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback(() => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback(() => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback(() => { }); - } - } - """, - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndParameterlessCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndParameterlessCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Callback(() => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Callback(() => { }); - mock.Setup(x => x.Do(It.IsAny>())).Callback(() => { }); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenCorrectSetupAndReturnsAndCallbacks() - { - await VerifyCSharpFix( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndReturnsAndCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndReturnsAndCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Returns(0).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns(0).Callback((int i, string s, DateTime dt) => { }); - mock.Setup(x => x.Do(It.IsAny>())).Returns(0).Callback((List l) => { }); - } - } - """, - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace CallbackSignatureShouldMatchMockedMethod.TestGoodSetupAndReturnsAndCallback; - - internal interface IFoo - { - int Do(string s); - - int Do(int i, string s, DateTime dt); - - int Do(List l); - } - - internal class MyUnitTests - { - private void TestGoodSetupAndReturnsAndCallback() - { - var mock = new Mock(); - mock.Setup(x => x.Do(It.IsAny())).Returns(0).Callback((string s) => { }); - mock.Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())).Returns(0).Callback((int i, string s, DateTime dt) => { }); - mock.Setup(x => x.Do(It.IsAny>())).Returns(0).Callback((List l) => { }); - } - } - """); + await Verifier.VerifyCodeFixAsync(Template(@namespace, original), Template(@namespace, quickFix)); } } diff --git a/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs b/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs index 540b04a0..0c31fd3c 100644 --- a/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/ConstructorArgumentsShouldMatchAnalyzerTests.cs @@ -1,122 +1,98 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class ConstructorArgumentsShouldMatchAnalyzerTests : DiagnosticVerifier +public class ConstructorArgumentsShouldMatchAnalyzerTests { - [Fact] - public async Task ShouldPassWhenConstructorArgumentsMatch() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using System; - using System.Collections.Generic; - using Moq; + foreach (var @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return [@namespace, """new Mock(MockBehavior.Default);"""]; + yield return [@namespace, """new Mock(MockBehavior.Strict);"""]; + yield return [@namespace, """new Mock(MockBehavior.Loose);"""]; + yield return [@namespace, """new Mock("3");"""]; + yield return [@namespace, """new Mock("4");"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, "5");"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, "6");"""]; + yield return [@namespace, """new Mock(false, 0);"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, true, 1);"""]; + yield return [@namespace, """new Mock(DateTime.Now, DateTime.Now);"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, DateTime.Now, DateTime.Now);"""]; + yield return [@namespace, """new Mock(new List(), "7");"""]; + yield return [@namespace, """new Mock(new List());"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, new List(), "8");"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, new List());"""]; + yield return [@namespace, """new Mock{|Moq1002:(1, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(2, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:("1", 3)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(new int[] { 1, 2, 3 })|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(MockBehavior.Strict, 4, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(MockBehavior.Loose, 5, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(MockBehavior.Loose, "2", 6)|};"""]; + yield return [@namespace, """new Mock>{|Moq1002:("42")|};"""]; + yield return [@namespace, """new Mock>{|Moq1002:("42", 42)|};"""]; + yield return [@namespace, """new Mock>{|Moq1002:(42)|};"""]; + yield return [@namespace, """new Mock>();"""]; + yield return [@namespace, """new Mock>(MockBehavior.Default);"""]; + // TODO: "I think this _should_ fail, but currently passes. Tracked by #55." + // yield return [@namespace, """new Mock();"""]; + yield return [@namespace, """new Mock{|Moq1002:("42")|};"""]; + yield return [@namespace, """new Mock{|Moq1002:("42", 42)|};"""]; + yield return [@namespace, """new Mock{|Moq1002:(42)|};"""]; + yield return [@namespace, """new Mock();"""]; + yield return [@namespace, """new Mock(42);"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, 42);"""]; + yield return [@namespace, """new Mock(42, "42");"""]; + yield return [@namespace, """new Mock(MockBehavior.Default, 42, "42");"""]; + yield return [@namespace, """new Mock>(42);"""]; + yield return [@namespace, """new Mock>(MockBehavior.Default, 42);"""]; + } + } - namespace ConstructorArgumentsShouldMatch.TestGood; + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldAnalyzeConstructorArguments(string @namespace, string mock) + { + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} internal class Foo { public Foo(string s) { } - public Foo(bool b, int i) { } - public Foo(params DateTime[] dates) { } - public Foo(List l, string s = "A") { } } - internal class MyUnitTests + internal abstract class AbstractClassDefaultCtor { - private void TestGood() - { - var mock1 = new Mock(MockBehavior.Default); - var mock2 = new Mock(MockBehavior.Strict); - var mock3 = new Mock(MockBehavior.Loose); - var mock4 = new Mock(MockBehavior.Default); - - var mock5 = new Mock("3"); - var mock6 = new Mock("4"); - var mock7 = new Mock(MockBehavior.Default, "5"); - var mock8 = new Mock(MockBehavior.Default, "6"); - - var mock9 = new Mock(false, 0); - var mock10 = new Mock(MockBehavior.Default, true, 1); - - var mock11 = new Mock(DateTime.Now, DateTime.Now); - var mock12 = new Mock(MockBehavior.Default, DateTime.Now, DateTime.Now); - - var mock13 = new Mock(new List(), "7"); - var mock14 = new Mock(new List()); - var mock15 = new Mock(MockBehavior.Default, new List(), "8"); - var mock16 = new Mock(MockBehavior.Default, new List()); - } + protected AbstractClassDefaultCtor() { } } - """); - } - - [Fact] - public async Task ShouldFailWhenConstructorArumentsDoNotMatch() - { - await VerifyCSharpDiagnostic( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace ConstructorArgumentsShouldMatch.TestBad; - internal class Foo + internal abstract class AbstractGenericClassDefaultCtor { - public Foo(string s) { } - - public Foo(bool b, int i) { } - - public Foo(params DateTime[] dates) { } - - public Foo(List l, string s = "A") { } + protected AbstractGenericClassDefaultCtor() { } } - internal class MyUnitTests + internal abstract class AbstractClassWithCtor { - private void TestBad() - { - var mock1 = new Mock{|Moq1002:(1, true)|}; - var mock2 = new Mock{|Moq1002:(2, true)|}; - var mock3 = new Mock{|Moq1002:("1", 3)|}; - var mock4 = new Mock{|Moq1002:(new int[] { 1, 2, 3 })|}; - } + protected AbstractClassWithCtor(int a) { } + protected AbstractClassWithCtor(int a, string b) { } } - """); - } - - [Fact] - public async Task ShouldFailWhenConstructorArumentsWithExplicitMockBehaviorDoNotMatch() - { - await VerifyCSharpDiagnostic( - """ - using System; - using System.Collections.Generic; - using Moq; - - namespace ConstructorArgumentsShouldMatchTestBadWithMockBehavior; - internal class Foo + internal abstract class AbstractGenericClassWithCtor { - public Foo(string s) { } - - public Foo(bool b, int i) { } - - public Foo(params DateTime[] dates) { } - - public Foo(List l, string s = "A") { } + protected AbstractGenericClassWithCtor(int a) { } + protected AbstractGenericClassWithCtor(int a, string b) { } } - internal class MyUnitTests + internal class UnitTest { - private void TestBadWithMockBehavior() + private void Test() { - var mock1 = new Mock{|Moq1002:(MockBehavior.Strict, 4, true)|}; - var mock2 = new Mock{|Moq1002:(MockBehavior.Loose, 5, true)|}; - var mock3 = new Mock{|Moq1002:(MockBehavior.Loose, "2", 6)|}; + {{mock}} } } """); diff --git a/Source/Moq.Analyzers.Test/Helpers/AnalyzerTestExtensions.cs b/Source/Moq.Analyzers.Test/Helpers/AnalyzerTestExtensions.cs deleted file mode 100644 index 1400b71c..00000000 --- a/Source/Moq.Analyzers.Test/Helpers/AnalyzerTestExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.CodeAnalysis.Testing; - -namespace Moq.Analyzers.Test.Helpers; - -internal static class AnalyzerTestExtensions -{ - public static TAnalyzerTest SetDefaults(this TAnalyzerTest test) - where TAnalyzerTest : AnalyzerTest - where TVerifier : IVerifier, new() - { - test.ReferenceAssemblies = ReferenceAssemblyCatalog.Net80WithOldMoq; - - return test; - } -} diff --git a/Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs b/Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs new file mode 100644 index 00000000..5f371e07 --- /dev/null +++ b/Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs @@ -0,0 +1,18 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Moq.Analyzers.Test.Helpers; + +internal static class AnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + public static async Task VerifyAnalyzerAsync(string source) + { + await new Test + { + TestCode = source, + FixedCode = source, + }.RunAsync().ConfigureAwait(false); + } +} diff --git a/Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs b/Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs index a2b39f4b..ce2c93c2 100644 --- a/Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs +++ b/Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs @@ -1,24 +1,18 @@ using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Testing; namespace Moq.Analyzers.Test.Helpers; -public abstract class CodeFixVerifier +internal static class CodeFixVerifier where TAnalyzer : DiagnosticAnalyzer, new() where TCodeFixProvider : CodeFixProvider, new() { - protected async Task VerifyCSharpFix(string originalSource, string fixedSource) + public static async Task VerifyCodeFixAsync(string originalSource, string fixedSource) { - CSharpCodeFixTest context = new() + await new Test { TestCode = originalSource, FixedCode = fixedSource, - }; - - context.SetDefaults, DefaultVerifier>(); - - await context.RunAsync().ConfigureAwait(false); + }.RunAsync().ConfigureAwait(false); } } diff --git a/Source/Moq.Analyzers.Test/Helpers/DiagnosticVerifier.cs b/Source/Moq.Analyzers.Test/Helpers/DiagnosticVerifier.cs deleted file mode 100644 index 9118130b..00000000 --- a/Source/Moq.Analyzers.Test/Helpers/DiagnosticVerifier.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Testing; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Testing; - -namespace Moq.Analyzers.Test.Helpers; - -public abstract class DiagnosticVerifier - where TAnalyzer : DiagnosticAnalyzer, new() -{ - protected async Task VerifyCSharpDiagnostic(string source) - { - CSharpAnalyzerTest context = new() - { - TestCode = source, - }; - - context.SetDefaults, DefaultVerifier>(); - - await context.RunAsync().ConfigureAwait(false); - } -} diff --git a/Source/Moq.Analyzers.Test/Helpers/Test.cs b/Source/Moq.Analyzers.Test/Helpers/Test.cs new file mode 100644 index 00000000..01e2093b --- /dev/null +++ b/Source/Moq.Analyzers.Test/Helpers/Test.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Moq.Analyzers.Test.Helpers; + +/// +/// An implementation of that sets default configuration +/// for our tests. +/// +/// The type of analyzer to test. +/// The type of code fix provider to test. If the test is for an analyzer without a code fix, use . +internal class Test : CSharpCodeFixTest + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFixProvider : CodeFixProvider, new() +{ + public Test() + { + // Add Moq and some common usings to all test cases to avoid test authoring errors. + const string globalUsings = + """ + global using System; + global using System.Collections.Generic; + global using System.Threading.Tasks; + global using Moq; + """; + + TestState.Sources.Add(globalUsings); + FixedState.Sources.Add(globalUsings); + ReferenceAssemblies = ReferenceAssemblyCatalog.Net80WithOldMoq; + } +} diff --git a/Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs b/Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs index 192e2797..01ab2cea 100644 --- a/Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/NoConstructorArgumentsForInterfaceMockAnalyzerTests.cs @@ -1,93 +1,53 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class NoConstructorArgumentsForInterfaceMockAnalyzerTests : DiagnosticVerifier +public class NoConstructorArgumentsForInterfaceMockAnalyzerTests { - [Fact] - public async Task ShouldFailIfMockedInterfaceHasConstructorParameters() + public static IEnumerable InterfaceMockingTestData() { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace NoConstructorArgumentsForInterfaceMock.TestBad; - - internal interface IMyService - { - void Do(string s); - } - - internal class MyUnitTests - { - private void TestBad() - { - var mock1 = new Mock{|Moq1001:(25, true)|}; - var mock2 = new Mock{|Moq1001:("123")|}; - var mock3 = new Mock{|Moq1001:(25, true)|}; - var mock4 = new Mock{|Moq1001:("123")|}; - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return [@namespace, """new Mock{|Moq1001:(25, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1001:("123")|};"""]; + yield return [@namespace, """new Mock{|Moq1001:(MockBehavior.Default, "123")|};"""]; + yield return [@namespace, """new Mock{|Moq1001:(MockBehavior.Strict, 25, true)|};"""]; + yield return [@namespace, """new Mock{|Moq1001:(MockBehavior.Loose, 25, true)|};"""]; + yield return [@namespace, """new Mock();"""]; + yield return [@namespace, """new Mock(MockBehavior.Default);"""]; + yield return [@namespace, """new Mock(MockBehavior.Strict);"""]; + yield return [@namespace, """new Mock(MockBehavior.Loose);"""]; + } } - [Fact] - public async Task ShouldFailIfMockedInterfaceHasConstructorParametersAndExplicitMockBehavior() + [Theory] + [MemberData(nameof(InterfaceMockingTestData))] + public async Task ShouldAnalyzeInterfaceConstructors(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace NoConstructorArgumentsForInterfaceMock.TestBadWithMockBehavior; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} internal interface IMyService { void Do(string s); } - internal class MyUnitTests + internal class UnitTest { - private void TestBadWithMockBehavior() + private void Test() { - var mock1 = new Mock{|Moq1001:(MockBehavior.Default, "123")|}; - var mock2 = new Mock{|Moq1001:(MockBehavior.Strict, 25, true)|}; - var mock3 = new Mock{|Moq1001:(MockBehavior.Default, "123")|}; - var mock4 = new Mock{|Moq1001:(MockBehavior.Loose, 25, true)|}; + {{mock}} } } """); } - [Fact] - public async Task ShouldPassIfMockedInterfaceDoesNotHaveConstructorParameters() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace NoConstructorArgumentsForInterfaceMock.TestGood; - - internal interface IMyService - { - void Do(string s); - } - - internal class MyUnitTests - { - private void TestGood() - { - var mock1 = new Mock(); - var mock2 = new Mock(MockBehavior.Default); - var mock3 = new Mock(MockBehavior.Strict); - var mock4 = new Mock(MockBehavior.Loose); - } - } - """); - } - - // TODO: This feels like it should be in every analyzer's tests + // TODO: This feels like it should be in every analyzer's tests. Tracked by #75. [Fact] public async Task ShouldPassIfCustomMockClassIsUsed() { - await VerifyCSharpDiagnostic( + await Verifier.VerifyAnalyzerAsync( """ namespace NoConstructorArgumentsForInterfaceMock.TestFakeMoq; @@ -130,11 +90,11 @@ private void TestFakeMoq() """); } - // TODO: This feels duplicated with other tests + // TODO: This feels like it should be in every analyzer's tests. Tracked by #75. [Fact] public async Task ShouldFailIsRealMoqIsUsedWithInvalidParameters() { - await VerifyCSharpDiagnostic( + await Verifier.VerifyAnalyzerAsync( """ namespace NoConstructorArgumentsForInterfaceMock.TestRealMoqWithBadParameters; @@ -177,10 +137,11 @@ private void TestRealMoqWithBadParameters() """); } + // TODO: This feels like it should be in every analyzer's tests. Tracked by #75. [Fact] public async Task ShouldPassIfRealMoqIsUsedWithValidParameters() { - await VerifyCSharpDiagnostic( + await Verifier.VerifyAnalyzerAsync( """ namespace NoConstructorArgumentsForInterfaceMock.TestRealMoqWithGoodParameters; diff --git a/Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs b/Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs index 65596113..707d66a5 100644 --- a/Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/NoMethodsInPropertySetupAnalyzerTests.cs @@ -1,50 +1,30 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class NoMethodsInPropertySetupAnalyzerTests : DiagnosticVerifier +public class NoMethodsInPropertySetupAnalyzerTests { - [Fact] - public async Task ShouldPassWhenPropertiesUsePropertySetup() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace NoMethodsInPropertySetup.Good; - - public interface IFoo - { - string Prop1 { get; set; } - - string Prop2 { get; } - - string Prop3 { set; } - - string Method(); - } - - public class MyUnitTests - { - private void TestGood() - { - var mock = new Mock(); - mock.SetupGet(x => x.Prop1); - mock.SetupGet(x => x.Prop2); - mock.SetupSet(x => x.Prop1 = "1"); - mock.SetupSet(x => x.Prop3 = "2"); - mock.Setup(x => x.Method()); - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return [@namespace, """new Mock().SetupGet(x => x.Prop1);"""]; + yield return [@namespace, """new Mock().SetupGet(x => x.Prop2);"""]; + yield return [@namespace, """new Mock().SetupSet(x => x.Prop1 = "1");"""]; + yield return [@namespace, """new Mock().SetupSet(x => x.Prop3 = "2");"""]; + yield return [@namespace, """new Mock().Setup(x => x.Method());"""]; + yield return [@namespace, """new Mock().SetupGet(x => {|Moq1101:x.Method()|});"""]; + yield return [@namespace, """new Mock().SetupSet(x => {|Moq1101:x.Method()|});"""]; + } } - [Fact] - public async Task ShouldFailWhenMethodsUsePropertySetup() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldAnalyzePropertySetup(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace NoMethodsInPropertySetup.Bad; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} public interface IFoo { @@ -57,13 +37,11 @@ public interface IFoo string Method(); } - public class MyUnitTests + public class UnitTest { - private void TestBad() + private void Test() { - var mock = new Mock(); - mock.SetupGet(x => {|Moq1101:x.Method()|}); - mock.SetupSet(x => {|Moq1101:x.Method()|}); + {{mock}} } } """); diff --git a/Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs b/Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs index e4b4a046..14d4b9c0 100644 --- a/Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/NoSealedClassMocksAnalyzerTests.cs @@ -1,50 +1,35 @@ -namespace Moq.Analyzers.Test; +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; -public class NoSealedClassMocksAnalyzerTests : DiagnosticVerifier +namespace Moq.Analyzers.Test; + +public class NoSealedClassMocksAnalyzerTests { - [Fact] - public async Task ShouldFailWhenClassIsSealed() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using System; - using Moq; - - namespace NoSealedClassMocks.Sealed; - - internal sealed class FooSealed { } - - internal class Foo { } - - internal class MyUnitTests - { - private void Sealed() - { - var mock = new Mock<{|Moq1000:FooSealed|}>(); - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return [@namespace, """new Mock<{|Moq1000:FooSealed|}>();"""]; + yield return [@namespace, """new Mock();"""]; + } } - [Fact] - public async Task ShouldPassWhenClassIsNotSealed() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShoulAnalyzeSealedClassMocks(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using System; - using Moq; - - namespace NoSealedClassMocks.NotSealed; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} internal sealed class FooSealed { } internal class Foo { } - internal class MyUnitTests + internal class UnitTest { - private void NotSealed() + private void Test() { - var mock = new Mock(); + {{mock}} } } """); diff --git a/Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs b/Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs index d986873f..8d78856f 100644 --- a/Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests.cs @@ -1,235 +1,58 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests : DiagnosticVerifier +public class SetupShouldBeUsedOnlyForOverridableMembersAnalyzerTests { - [Fact] - public async Task ShouldFailWhenSetupIsCalledWithANonVirtualMethod() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestBadSetupForNonVirtualMethod; - - public abstract class BaseSampleClass - { - public int Calculate() => 0; - - public abstract int Calculate(int a, int b); - - public abstract int Calculate(int a, int b, int c); - } - - internal class MyUnitTests - { - private void TestBadSetupForNonVirtualMethod() - { - var mock = new Mock(); - mock.Setup(x => {|Moq1200:x.Calculate()|}); - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" }) + { + yield return [@namespace, """new Mock().Setup(x => {|Moq1200:x.Calculate()|});"""]; + yield return [@namespace, """new Mock().Setup(x => {|Moq1200:x.Property|});"""]; + yield return [@namespace, """new Mock().Setup(x => {|Moq1200:x.Calculate(It.IsAny(), It.IsAny(), It.IsAny())|});"""]; + yield return [@namespace, """new Mock().Setup(x => x.Calculate(It.IsAny(), It.IsAny()));"""]; + yield return [@namespace, """new Mock().Setup(x => x.Calculate(It.IsAny(), It.IsAny()));"""]; + yield return [@namespace, """new Mock().Setup(x => x.TestProperty);"""]; + yield return [@namespace, """new Mock().Setup(x => x.Calculate(It.IsAny(), It.IsAny()));"""]; + yield return [@namespace, """new Mock().Setup(x => x.DoSth());"""]; + } } - [Fact] - public async Task ShouldFailWhenSetupIsCalledWithANonVirtualProperty() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldAnalyzeSetupForOverridableMembers(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestBadSetupForNonVirtualProperty; - - public class SampleClass - { - - public int Property { get; set; } - } - - internal class MyUnitTests - { - private void TestBadSetupForNonVirtualProperty() - { - var mock = new Mock(); - mock.Setup(x => {|Moq1200:x.Property|}); - } - } - """); - } - - [Fact] - public async Task ShouldFailWhenSetupIsCalledWithASealedMethod() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestBadSetupForSealedMethod; - - public abstract class BaseSampleClass - { - public int Calculate() => 0; - - public abstract int Calculate(int a, int b); - - public abstract int Calculate(int a, int b, int c); - } - - public class SampleClass : BaseSampleClass - { - - public override int Calculate(int a, int b) => 0; - - public sealed override int Calculate(int a, int b, int c) => 0; - } - - internal class MyUnitTests - { - private void TestBadSetupForSealedMethod() - { - var mock = new Mock(); - mock.Setup(x => {|Moq1200:x.Calculate(It.IsAny(), It.IsAny(), It.IsAny())|}); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenSetupIsCalledWithAnAbstractMethod() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestOkForAbstractMethod; - - public abstract class BaseSampleClass - { - public int Calculate() => 0; - - public abstract int Calculate(int a, int b); - - public abstract int Calculate(int a, int b, int c); - } - - internal class MyUnitTests - { - private void TestOkForAbstractMethod() - { - var mock = new Mock(); - mock.Setup(x => x.Calculate(It.IsAny(), It.IsAny())); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenSetupIsCalledWithAnInterfaceMethod() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestOkForInterfaceMethod; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} public interface ISampleInterface { int Calculate(int a, int b); - } - - internal class MyUnitTests - { - private void TestOkForInterfaceMethod() - { - var mock = new Mock(); - mock.Setup(x => x.Calculate(It.IsAny(), It.IsAny())); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenSetupIsCalledWithAnInterfaceProperty() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestOkForInterfaceProperty; - - public interface ISampleInterface - { int TestProperty { get; set; } } - internal class MyUnitTests - { - private void TestOkForInterfaceProperty() - { - var mock = new Mock(); - mock.Setup(x => x.TestProperty); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenSetupIsCalledWithAnOverrideOfAnAbstractMethod() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestOkForOverrideAbstractMethod; - public abstract class BaseSampleClass { public int Calculate() => 0; - public abstract int Calculate(int a, int b); - public abstract int Calculate(int a, int b, int c); } public class SampleClass : BaseSampleClass { - public override int Calculate(int a, int b) => 0; - public sealed override int Calculate(int a, int b, int c) => 0; - } - - internal class MyUnitTests - { - private void TestOkForOverrideAbstractMethod() - { - var mock = new Mock(); - mock.Setup(x => x.Calculate(It.IsAny(), It.IsAny())); - } - } - """); - } - - [Fact] - public async Task ShouldPassWhenSetupIsCalledWithAVirtualMethod() - { - await VerifyCSharpDiagnostic( - """ - using Moq; - - namespace SetupOnlyForOverridableMembers.TestOkForVirtualMethod; - - public class SampleClass - { public virtual int DoSth() => 0; + public int Property { get; set; } } - internal class MyUnitTests + internal class UnitTest { - private void TestOkForVirtualMethod() + private void Test() { - var mock = new Mock(); - mock.Setup(x => x.DoSth()); + {{mock}} } } """); diff --git a/Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs b/Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs index c0c9afd3..36822dc4 100644 --- a/Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs +++ b/Source/Moq.Analyzers.Test/SetupShouldNotIncludeAsyncResultAnalyzerTests.cs @@ -1,73 +1,26 @@ +using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier; + namespace Moq.Analyzers.Test; -public class SetupShouldNotIncludeAsyncResultAnalyzerTests : DiagnosticVerifier +public class SetupShouldNotIncludeAsyncResultAnalyzerTests { - [Fact] - public async Task ShouldPassWhenSetupWithoutReturn() + public static IEnumerable TestData() { - await VerifyCSharpDiagnostic( - """ - using System.Threading.Tasks; - using Moq; - - namespace SetupShouldNotIncludeAsyncResult.TestOkForTask; - - public class AsyncClient - { - public virtual Task TaskAsync() => Task.CompletedTask; - - public virtual Task GenericTaskAsync() => Task.FromResult(string.Empty); - } - - internal class MyUnitTests - { - private void TestOkForTask() - { - var mock = new Mock(); - mock.Setup(c => c.TaskAsync()); - } - } - """); + foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace; "}) + { + yield return [@namespace, """new Mock().Setup(c => c.TaskAsync());"""]; + yield return [@namespace, """new Mock().Setup(c => c.GenericTaskAsync()).ReturnsAsync(string.Empty);"""]; + yield return [@namespace, """new Mock().Setup(c => {|Moq1201:c.GenericTaskAsync().Result|});"""]; + } } - [Fact] - public async Task ShouldPassWhenSetupWithReturnsAsync() + [Theory] + [MemberData(nameof(TestData))] + public async Task ShouldAnalyzeSetupForAsyncResult(string @namespace, string mock) { - await VerifyCSharpDiagnostic( - """ - using System.Threading.Tasks; - using Moq; - - namespace SetupShouldNotIncludeAsyncResult.TestOkForGenericTaskProperSetup; - - public class AsyncClient - { - public virtual Task TaskAsync() => Task.CompletedTask; - - public virtual Task GenericTaskAsync() => Task.FromResult(string.Empty); - } - - internal class MyUnitTests - { - private void TestOkForGenericTaskProperSetup() - { - var mock = new Mock(); - mock.Setup(c => c.GenericTaskAsync()) - .ReturnsAsync(string.Empty); - } - } - """); - } - - [Fact] - public async Task ShouldFailWhenSetupWithTaskResult() - { - await VerifyCSharpDiagnostic( - """ - using System.Threading.Tasks; - using Moq; - - namespace SetupShouldNotIncludeAsyncResult.TestBadForGenericTask; + await Verifier.VerifyAnalyzerAsync( + $$""" + {{@namespace}} public class AsyncClient { @@ -76,12 +29,11 @@ public class AsyncClient public virtual Task GenericTaskAsync() => Task.FromResult(string.Empty); } - internal class MyUnitTests + internal class UnitTest { - private void TestBadForGenericTask() + private void Test() { - var mock = new Mock(); - mock.Setup(c => {|Moq1201:c.GenericTaskAsync().Result|}); + {{mock}} } } """);