Skip to content

Commit

Permalink
Update analyzer, refactor code, and add abstract class support (#20)
Browse files Browse the repository at this point in the history
* Update analyzer, refactor code, and add abstract class support

This commit includes several updates and improvements. The `ConstructorArgumentsShouldMatchAnalyzerTests.ShouldFailIfClassParametersDoNotMatch.approved.txt` file has been updated to reflect changes in the source code. The `FooAbstract` class and its usage have been removed from `ConstructorArgumentsShouldMatch.cs`. Defensive programming practices have been added to `DiagnosticVerifier.Helper.cs` to ensure non-null arguments. A new file `AbstractClass.cs` has been added to `Moq.Analyzers.Test.csproj`. The `Analyze` method in `ConstructorArgumentsShouldMatchAnalyzer.cs` has been refactored for better readability and maintainability, and now supports abstract classes. The `Moq.Analyzers.csproj` now uses the latest C# language version and the assembly version has been updated to `0.0.6.0`. New test class `AbstractClassTests` and related files have been added to verify the functionality of the updated analyzer.

Fixes #1

* retrigger checks

* Updated test files and refactored AbstractClassTests.cs

Updated `AbstractClassTests.ShouldFailIfBadParameters.verified.txt` and `AbstractClassTests.ShouldPassIfGoodParameters.verified.txt` to reflect changes in test conditions. Refactored `AbstractClassTests.cs` to remove `ApprovalTests` namespace, change `ShouldPassIfGoodParameters` to a Task method, and modify `VerifyCSharpDiagnostic` to take an array of file contents. Added `AbstractClass.Bad.cs` for testing incorrect parameters and updated `AbstractClass.Good.cs` with necessary changes. Updated `Moq.Analyzers.Test.csproj` to include new and updated files with `CopyToOutputDirectory` set to `Always`.

* `Updated test methods and diagnostic messages in AbstractClassTests`

Renamed the test method `ShouldPassIfGoodParameters` to `ShouldPassIfGoodParametersAndFailOnTypeMismatch` in `AbstractClassTests`. Removed the diagnostic message for the test `ShouldFailIfBadParameters`. Cleared the content of `AbstractClassTests.ShouldPassIfGoodParameters.verified.txt`. Added a new diagnostic message to `AbstractClassTests.ShouldPassIfGoodParametersAndFailOnTypeMismatch.verified.txt` indicating a `Moq1002` warning for a line in `Test1.cs` where the parameters provided to the `Mock<AbstractClassWithCtor>` constructor do not match any existing constructors.

* Delete Source/Moq.Analyzers.Test/AbstractClassTests.ShouldPassIfGoodParameters.approved.txt

This file is no longer needed

* Update DiagnosticVerifier.Helper.cs to move using inside namespace

* Update ConstructorArgumentsShouldMatchAnalyzer.cs comment about abstract type ctor

* Update Moq.Analyzers.csproj to remove language version override

* Update baselines for abstract class and ctor arguments due to refactor

* Rebaseline constructor arguments using Verify.Terminal

* Added new cases to analyze abstract classes' constructor arguments

* Rebaseline constructor arguments using Verify.Terminal

* Fix line endings on test csharp

* Add positive and negative cases for generic
  • Loading branch information
rjmurillo authored May 31, 2024
1 parent 7d14381 commit 6e96f79
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 49 deletions.
7 changes: 7 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"nbgv"
],
"rollForward": false
},
"verify.tool": {
"version": "0.6.0",
"commands": [
"dotnet-verify"
],
"rollForward": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Diagnostic 1
Id: Moq1002
Location: SourceFile(Test1.cs[247..253))
Highlight: ("42")
Lines: var mock = new Mock<AbstractClassWithCtor>("42");
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 2
Id: Moq1002
Location: SourceFile(Test1.cs[420..430))
Highlight: ("42", 42)
Lines: var mock1 = new Mock<AbstractClassWithCtor>("42", 42);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 3
Id: Moq1002
Location: SourceFile(Test1.cs[559..563))
Highlight: (42)
Lines: var mock2 = new Mock<AbstractClassDefaultCtor>(42);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 4
Id: Moq1002
Location: SourceFile(Test1.cs[780..786))
Highlight: ("42")
Lines: var mock = new Mock<AbstractGenericClassWithCtor<object>>("42");
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 5
Id: Moq1002
Location: SourceFile(Test1.cs[968..978))
Highlight: ("42", 42)
Lines: var mock1 = new Mock<AbstractGenericClassWithCtor<object>>("42", 42);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 6
Id: Moq1002
Location: SourceFile(Test1.cs[1122..1126))
Highlight: (42)
Lines: var mock2 = new Mock<AbstractGenericClassDefaultCtor<object>>(42);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

29 changes: 29 additions & 0 deletions Source/Moq.Analyzers.Test/AbstractClassTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.CodeAnalysis.Diagnostics;

namespace Moq.Analyzers.Test
{
using System.IO;

using TestHelper;

using Xunit;

public class AbstractClassTests : DiagnosticVerifier
{
[Fact]
public Task ShouldPassIfGoodParametersAndFailOnTypeMismatch()
{
return Verify(VerifyCSharpDiagnostic(
[
File.ReadAllText("Data/AbstractClass.Good.cs"),
File.ReadAllText("Data/AbstractClass.Bad.cs")
]
));
}

protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
{
return new ConstructorArgumentsShouldMatchAnalyzer();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
Diagnostic 1
Id: Moq1002
Location: SourceFile(Test0.cs[1321..1330))
Location: SourceFile(Test0.cs[1227..1236))
Highlight: (1, true)
Lines: var mock1 = new Moq.Mock<Foo>(1, true);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 2
Id: Moq1002
Location: SourceFile(Test0.cs[1401..1410))
Location: SourceFile(Test0.cs[1307..1316))
Highlight: (2, true)
Lines: var mock2 = new Mock<ConstructorArgumentsShouldMatch.Foo>(2, true);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 3
Id: Moq1002
Location: SourceFile(Test0.cs[1481..1489))
Location: SourceFile(Test0.cs[1387..1395))
Highlight: ("1", 3)
Lines: var mock3 = new Mock<ConstructorArgumentsShouldMatch.Foo>("1", 3);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 4
Id: Moq1002
Location: SourceFile(Test0.cs[1560..1583))
Location: SourceFile(Test0.cs[1466..1489))
Highlight: (new int[] { 1, 2, 3 })
Lines: var mock4 = new Mock<ConstructorArgumentsShouldMatch.Foo>(new int[] { 1, 2, 3 });
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 5
Id: Moq1002
Location: SourceFile(Test0.cs[1675..1705))
Location: SourceFile(Test0.cs[1581..1611))
Highlight: (MockBehavior.Strict, 4, true)
Lines: var mock1 = new Mock<Foo>(MockBehavior.Strict, 4, true);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 6
Id: Moq1002
Location: SourceFile(Test0.cs[1780..1809))
Location: SourceFile(Test0.cs[1686..1715))
Highlight: (MockBehavior.Loose, 5, true)
Lines: var mock2 = new Moq.Mock<ConstructorArgumentsShouldMatch.Foo>(MockBehavior.Loose, 5, true);
Severity: Warning
Message: Parameters provided into mock do not match any existing constructors.

Diagnostic 7
Id: Moq1002
Location: SourceFile(Test0.cs[1884..1912))
Location: SourceFile(Test0.cs[1790..1818))
Highlight: (MockBehavior.Loose, "2", 6)
Lines: var mock3 = new Moq.Mock<ConstructorArgumentsShouldMatch.Foo>(MockBehavior.Loose, "2", 6);
Severity: Warning
Expand Down
29 changes: 29 additions & 0 deletions Source/Moq.Analyzers.Test/Data/AbstractClass.Bad.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Moq.Analyzers.Test.Data
{
internal class MyBadUnitTests
{
private void TestBad()
{
// The class has a ctor that takes an Int32 but passes a String
var mock = new Mock<AbstractClassWithCtor>("42");

// The class has a ctor with two arguments [Int32, String], but they are passed in reverse order
var mock1 = new Mock<AbstractClassWithCtor>("42", 42);

// The class has a ctor but does not take any arguments
var mock2 = new Mock<AbstractClassDefaultCtor>(42);
}

private void TestBadWithGeneric()
{
// The class has a constructor that takes an Int32 but passes a String
var mock = new Mock<AbstractGenericClassWithCtor<object>>("42");

// The class has a ctor with two arguments [Int32, String], but they are passed in reverse order
var mock1 = new Mock<AbstractGenericClassWithCtor<object>>("42", 42);

// The class has a ctor but does not take any arguments
var mock2 = new Mock<AbstractGenericClassDefaultCtor<object>>(42);
}
}
}
80 changes: 80 additions & 0 deletions Source/Moq.Analyzers.Test/Data/AbstractClass.Good.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace Moq.Analyzers.Test.Data
{
internal abstract class AbstractGenericClassDefaultCtor<T>
{
protected AbstractGenericClassDefaultCtor()
{
}
}

internal abstract class AbstractGenericClassWithCtor<T>
{
protected AbstractGenericClassWithCtor(int a)
{
}

protected AbstractGenericClassWithCtor(int a, string b)
{
}
}

internal abstract class AbstractClassDefaultCtor
{
protected AbstractClassDefaultCtor()
{
}
}

internal abstract class AbstractClassWithCtor
{
protected AbstractClassWithCtor(int a)
{
}

protected AbstractClassWithCtor(int a, string b)
{
}
}

internal class MyUnitTests
{
// Base case that we can handle abstract types
private void TestForBaseNoArgs()
{
var mock = new Mock<AbstractClassDefaultCtor>();
mock.As<AbstractClassDefaultCtor>();

var mock2 = new Mock<AbstractClassWithCtor>();
var mock3 = new Mock<AbstractClassDefaultCtor>(MockBehavior.Default);
}

private void TestForBaseGenericNoArgs()
{
var mock = new Mock<AbstractGenericClassDefaultCtor<object>>();
mock.As<AbstractGenericClassDefaultCtor<object>>();

var mock1 = new Mock<AbstractGenericClassDefaultCtor<object>>();

var mock2 = new Mock<AbstractGenericClassDefaultCtor<object>>(MockBehavior.Default);
}

// This is syntatically not allowed by C#, but you can do it with Moq
private void TestForBaseWithArgsNonePassed()
{
var mock = new Mock<AbstractClassWithCtor>();
mock.As<AbstractClassWithCtor>();
}

private void TestForBaseWithArgsPassed()
{
var mock = new Mock<AbstractClassWithCtor>(42);
var mock2 = new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42);

var mock3 = new Mock<AbstractClassWithCtor>(42, "42");
var mock4 = new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42, "42");

var mock5 = new Mock<AbstractGenericClassWithCtor<object>>(42);
var mock6 = new Mock<AbstractGenericClassWithCtor<object>>(MockBehavior.Default, 42);
}
}
}
11 changes: 0 additions & 11 deletions Source/Moq.Analyzers.Test/Data/ConstructorArgumentsShouldMatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ public Foo(params DateTime[] dates) { }
public Foo(List<string> l, string s = "A") { }
}

internal abstract class FooAbstract
{
public FooAbstract(string s) { }
}

internal class MyUnitTests
#pragma warning restore SA1402 // File may only contain a single class
{
Expand Down Expand Up @@ -72,11 +67,5 @@ private void TestGood1()
var mock15 = new Mock<Foo>(MockBehavior.Default, new List<string>(), "8");
var mock16 = new Mock<Foo>(MockBehavior.Default, new List<string>());
}

private void TestAbstractGood()
{
var mock1 = new Mock<FooAbstract>("9");
var mock2 = new Mock<FooAbstract>(MockBehavior.Default, "10");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -42,6 +43,8 @@ public abstract partial class DiagnosticVerifier
/// <returns>An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location.</returns>
protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents)
{
Debug.Assert(documents != null, nameof(documents) + " != null");

var projects = new HashSet<Project>();
foreach (var document in documents)
{
Expand All @@ -51,6 +54,7 @@ protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyz
var diagnostics = new List<Diagnostic>();
foreach (var project in projects)
{
Debug.Assert(analyzer != null, nameof(analyzer) + " != null");
var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer));
var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result;
foreach (var diag in diags)
Expand Down Expand Up @@ -174,4 +178,4 @@ private static Project CreateProject(string[] sources, string language = Languag
return solution.GetProject(projectId);
}
}
}
}
6 changes: 6 additions & 0 deletions Source/Moq.Analyzers.Test/Moq.Analyzers.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<ProjectReference Include="..\Moq.Analyzers\Moq.Analyzers.csproj" AddPackageAsOutput="true" />
</ItemGroup>
<ItemGroup>
<Compile Update="Data\AbstractClass.Bad.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Update="Data\AbstractClass.Good.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Update="Data\CallbackSignatureShouldMatchMockedMethod.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
Expand Down
Loading

0 comments on commit 6e96f79

Please sign in to comment.