-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the PosInfoMoq1005 rule to check usage of the SetupSet<T>() method (
fixes #42).
- Loading branch information
1 parent
7595618
commit a06e5b9
Showing
8 changed files
with
300 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# PosInfoMoq1005: Defines the generic argument of the `SetupSet()` method with the type of the mocked property. | ||
|
||
| Property | Value | | ||
|-------------------------------------|------------------------------------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq1005 | | ||
| **Title** | Defines the generic argument of the `SetupSet()` method with the type of the mocked property. | | ||
| **Category** | Design | | ||
| **Default severity** | Warning | | ||
|
||
## Cause | ||
|
||
A property setter has been set up using `SetupSet()` without a generic argument that represents the type of the mocked property. | ||
|
||
## Rule description | ||
|
||
Moq provides two methods to mock a property setter: | ||
- `Mock<T>.SetupSet(Action<T>)` | ||
- `Mock<T>.SetupSet<TProperty>(Action<T, TProperty>)` | ||
|
||
When setting up a property setter, use `Mock<T>.SetupSet<TProperty>(Action<T, TProperty>)` by explicitly defining the type of the property to mock. | ||
This overload of the `SetupSet()` method allows you to define a typed `Callback()` and avoid exceptions if the delegate argument in the `Callback()` | ||
does not match the property type. | ||
|
||
For example, consider the following code to test: | ||
|
||
```csharp | ||
[Fact] | ||
public interface Customer | ||
{ | ||
string Name { get; set; } | ||
} | ||
``` | ||
|
||
If you mock the setter of the `Customer.Name` property, you should set up the property with the `SetupSet<string>()` method: | ||
|
||
```csharp | ||
[Fact] | ||
public void SetNameOfCustomer() | ||
{ | ||
var customer = new Mock<Customer>(); | ||
customer.SetupSet<string>(c => c.Name = "Gilles") // The SetupSet<string>() version is used. | ||
.Callback((string value) => | ||
{ | ||
// Called when the setter of the property is set. | ||
}); | ||
} | ||
``` | ||
|
||
The following code violates the rule because the `SetupSet()` method has no generic argument: | ||
|
||
```csharp | ||
[Fact] | ||
public void SetNameOfCustomer() | ||
{ | ||
var customer = new Mock<Customer>(); | ||
customer.SetupSet(c => c.Name = "Gilles") // The SetupSet() has been used without set the generic argument. | ||
.Callback((string value) => | ||
{ | ||
// Called when the setter of the property is set. | ||
}); | ||
} | ||
``` | ||
|
||
If the non-generic version of the `SetupSet()` method is used, the delegate in the `Callback()` method cannot be checked at compile time, | ||
an exception will occur during the execution of the unit test: | ||
|
||
```csharp | ||
[Fact] | ||
public void SetNameOfCustomer() | ||
{ | ||
var customer = new Mock<Customer>(); | ||
customer.SetupSet(c => c.Name = "Gilles") | ||
.Callback((int value) => // The code compiles, but during the execution of the unit test | ||
{ // an ArgumentException will be thrown. | ||
}); | ||
} | ||
``` | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, use the `SetupSet<TProperty>()` method with the type of the mocked property as the generic argument. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress a warning from this rule. Using the `SetupSet<T>()` method ensures that the delegate argument in the `Callback()` | ||
method matches the type of the property. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="SetupSetAnalyzer.cs" company="P.O.S Informatique"> | ||
// Copyright (c) P.O.S Informatique. All rights reserved. | ||
// </copyright> | ||
//----------------------------------------------------------------------- | ||
|
||
namespace PosInformatique.Moq.Analyzers | ||
{ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class SetupSetAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor UseSetupSetWithGenericArgumentRule = new DiagnosticDescriptor( | ||
"PosInfoMoq1005", | ||
"Defines the generic argument of the SetupSet() method with the type of the mocked property", | ||
"Defines the generic argument of the SetupSet() method with the type of the mocked property", | ||
"Compilation", | ||
DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
description: "The SetupSet<T>() method must be used when setting up a mocked property.", | ||
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq1005.html"); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(UseSetupSetWithGenericArgumentRule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
|
||
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression); | ||
} | ||
|
||
private static void Analyze(SyntaxNodeAnalysisContext context) | ||
{ | ||
var invocationExpression = (InvocationExpressionSyntax)context.Node; | ||
|
||
var moqSymbols = MoqSymbols.FromCompilation(context.Compilation); | ||
|
||
if (moqSymbols is null) | ||
{ | ||
return; | ||
} | ||
|
||
var methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken); | ||
|
||
// Check if not obsolete extension | ||
if (moqSymbols.IsObsoleteMockExtension(methodSymbol.Symbol)) | ||
{ | ||
return; | ||
} | ||
|
||
// Check is Setup() method. | ||
if (!moqSymbols.IsSetupSetMethodWithoutGenericArgument(methodSymbol.Symbol)) | ||
{ | ||
var nameSyntax = ((MemberAccessExpressionSyntax)invocationExpression.Expression).Name; | ||
|
||
context.ReportDiagnostic(UseSetupSetWithGenericArgumentRule, nameSyntax.GetLocation()); | ||
|
||
return; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
tests/Moq.Analyzers.Tests/Analyzers/SetupSetAnalyzerTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="SetupSetAnalyzerTest.cs" company="P.O.S Informatique"> | ||
// Copyright (c) P.O.S Informatique. All rights reserved. | ||
// </copyright> | ||
//----------------------------------------------------------------------- | ||
|
||
namespace PosInformatique.Moq.Analyzers.Tests | ||
{ | ||
using Verifier = MoqCSharpAnalyzerVerifier<SetupSetAnalyzer>; | ||
|
||
public class SetupSetAnalyzerTest | ||
{ | ||
[Fact] | ||
public async Task SetupSetWithGenericArgument_NoDiagnosticReported() | ||
{ | ||
var source = @" | ||
namespace ConsoleApplication1 | ||
{ | ||
using Moq; | ||
using System; | ||
public class TestClass | ||
{ | ||
public void TestMethod() | ||
{ | ||
var mock1 = new Mock<I>(); | ||
mock1.SetupSet<int>(i => i.TestProperty = 1234); | ||
mock1.SetupSet(i => i.TestProperty); // Ignored because Obsolete by Moq | ||
} | ||
} | ||
public interface I | ||
{ | ||
int TestProperty { get; set; } | ||
} | ||
}"; | ||
|
||
await Verifier.VerifyAnalyzerAsync(source); | ||
} | ||
|
||
[Fact] | ||
public async Task SetupSetWithNoGenericArgument_DiagnosticReported() | ||
{ | ||
var source = @" | ||
namespace ConsoleApplication1 | ||
{ | ||
using Moq; | ||
using System; | ||
public class TestClass | ||
{ | ||
public void TestMethod() | ||
{ | ||
var mock1 = new Mock<I>(); | ||
mock1.[|SetupSet|](i => i.TestProperty = 1234); | ||
} | ||
} | ||
public interface I | ||
{ | ||
int TestProperty { get; set; } | ||
} | ||
}"; | ||
|
||
await Verifier.VerifyAnalyzerAsync(source); | ||
} | ||
|
||
[Fact] | ||
public async Task NoMoqLibrary() | ||
{ | ||
var source = @" | ||
namespace ConsoleApplication1 | ||
{ | ||
using OtherNamespace; | ||
public class TestClass | ||
{ | ||
public void TestMethod() | ||
{ | ||
var mock1 = new Mock<I>(); | ||
mock1.SetupSet(i => i.Property = 1234); | ||
} | ||
} | ||
public interface I | ||
{ | ||
int Property { get; set; } | ||
} | ||
} | ||
namespace OtherNamespace | ||
{ | ||
public class Mock<T> | ||
{ | ||
public void SetupSet(System.Action<T> _) { } | ||
} | ||
}"; | ||
|
||
await Verifier.VerifyAnalyzerWithNoMoqLibraryAsync(source); | ||
} | ||
} | ||
} |