-
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 PosInfoMoq1003 and PosInfoMoq1004 rules implementation.
- Loading branch information
1 parent
fc643f3
commit 282546d
Showing
18 changed files
with
766 additions
and
20 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
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 |
---|---|---|
|
@@ -19,13 +19,13 @@ should be called in the *Assert* phase to check the setup methods has been calle | |
|
||
```csharp | ||
[Fact] | ||
public void GetCustomer_ShouldCallRepository() | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
// Arrange | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", "Gilles")); | ||
|
||
var service = new CustomerService(repository.Object); | ||
var service = new CustomerService(smtpService.Object); | ||
|
||
// Act | ||
service.SendMail("Gilles"); | ||
|
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 |
---|---|---|
|
@@ -19,14 +19,14 @@ In the *Arrange* phase of an unit test, when `Verifiable()` method has been setu | |
|
||
```csharp | ||
[Fact] | ||
public void GetCustomer_ShouldCallRepository() | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
// Arrange | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", "Gilles")) | ||
.Verifiable(); | ||
|
||
var service = new CustomerService(repository.Object); | ||
var service = new CustomerService(smtpService.Object); | ||
|
||
// Act | ||
service.SendMail("Gilles"); | ||
|
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,93 @@ | ||
# PosInfoMoq1003: The `Callback()` method should be used to check the parameters when mocking a method with `It.IsAny<T>()` arguments | ||
|
||
| Property | Value | | ||
|-------------------------------------|---------------------------------------------------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq1003 | | ||
| **Title** | The `Callback()` method should be used to check the parameters when mocking a method with `It.IsAny<T>()` arguments | | ||
| **Category** | Design | | ||
| **Default severity** | Warning | | ||
|
||
## Cause | ||
|
||
A method has been setup with `It.IsAny<T>()` arguments without checking the parameters in a `Callback()` | ||
method. | ||
|
||
## Rule description | ||
|
||
When setup a method using `It.IsAny<T>()` arguments, the parameters should be check in the `Callback()` method. | ||
|
||
For example if we have the following code to test: | ||
|
||
```csharp | ||
[Fact] | ||
public class CustomerService | ||
{ | ||
private readonly ISmtpService smtpService; | ||
|
||
public CustomerService(ISmtpService smtpService) | ||
{ | ||
this.smtpService = smtpService; | ||
} | ||
|
||
public void SendMail(string emailAddress) | ||
{ | ||
this.smtpService.SendMail("[email protected]", emailAddress); | ||
} | ||
} | ||
``` | ||
|
||
If we mock the `ISmtpService.SendMail()` with a `It.IsAny<string>()` for the `emailAddress` argument, | ||
we can not check if the `CustomerService.SendMail()` has propagate correctly the value of the argument to the | ||
parameter of the `ISmtpService.SendMail()` method. | ||
|
||
```csharp | ||
[Fact] | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())); // With It.IsAny<string>() we can not check that emailAddress has been correctly passed in the CustomerService.SendMail() method. | ||
var service = new CustomerService(smtpService.Object); | ||
|
||
service.SendMail("Gilles"); | ||
} | ||
``` | ||
|
||
The `emailAddress` parameter passed to the `ISmtpService.SendMail()` method should be tested | ||
with the `Callback()` method, when mocking the `ISmtpService.SendMail()` method with a `It.IsAny<T>()` argument. | ||
|
||
```csharp | ||
[Fact] | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())) // With It.IsAny() we should test the arguments if correctly propagated in the Callback() method. | ||
.Callback((string _, string emailAddress) => | ||
{ | ||
Assert.AreEqual("Gilles", em); // Check the emailAddress parameter. | ||
}); | ||
|
||
var service = new CustomerService(smtpService.Object); | ||
|
||
service.SendMail("Gilles"); | ||
} | ||
``` | ||
|
||
### Remarks | ||
- If the parameters of mocked methods are very simple (primitive values for example), pass directly the expected value in the arguments of the method. For example | ||
```csharp | ||
smtpService.Setup(s => s.SendMail("[email protected]", "Gilles")) | ||
``` | ||
Instead of | ||
```csharp | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())) | ||
``` | ||
- Use the `Callback()` to assert complex parameters. | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, use the `Callback()` method to check the `It.IsAny<T>()` arguments. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress a warning from this rule. Normally `It.IsAny<T>()` arguments should be check and asserted in the `Callback()` methods. |
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,93 @@ | ||
# PosInfoMoq1004: The `Callback()` parameter should not be ignored if it has been setup as an `It.IsAny<T>()` argument. | ||
|
||
| Property | Value | | ||
|-------------------------------------|------------------------------------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq1004 | | ||
| **Title** | The `Callback()` parameter should not be ignored if it has been setup as an `It.IsAny<T>()` argument. | | ||
| **Category** | Design | | ||
| **Default severity** | Warning | | ||
|
||
## Cause | ||
|
||
A method has been setup with `It.IsAny<T>()` arguments and the parameter in the `Callback()` has not be used. | ||
|
||
## Rule description | ||
|
||
When setup a method using `It.IsAny<T>()` arguments, the parameters should be check in the `Callback()` method. | ||
|
||
For example if we have the following code to test: | ||
|
||
```csharp | ||
[Fact] | ||
public class CustomerService | ||
{ | ||
private readonly ISmtpService smtpService; | ||
|
||
public CustomerService(ISmtpService smtpService) | ||
{ | ||
this.smtpService = smtpService; | ||
} | ||
|
||
public void SendMail(string emailAddress) | ||
{ | ||
this.smtpService.SendMail("[email protected]", emailAddress); | ||
} | ||
} | ||
``` | ||
|
||
If we mock the `ISmtpService.SendMail()` with a `It.IsAny<string>()` for the `emailAddress` argument, | ||
we can not check if the `CustomerService.SendMail()` has propagate correctly the value of the argument to the | ||
parameter of the `ISmtpService.SendMail()` method. | ||
|
||
```csharp | ||
[Fact] | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())) | ||
.Callback((string _, string _) => { ... }); // The second parameter (emailAddress) should not be ignored and should be tested. | ||
var service = new CustomerService(smtpService.Object); | ||
|
||
service.SendMail("Gilles"); | ||
} | ||
``` | ||
|
||
The `emailAddress` parameter passed to the `ISmtpService.SendMail()` method should be tested | ||
with the `Callback()` method, when mocking the `ISmtpService.SendMail()` method with a `It.IsAny<T>()` argument. | ||
|
||
```csharp | ||
[Fact] | ||
public void SendMail_ShouldCallSmtpService() | ||
{ | ||
var smtpService = new Mock<ISmtpService>(); | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())) // With It.IsAny() we should test the arguments if correctly propagated in the Callback() method. | ||
.Callback((string _, string emailAddress) => | ||
{ | ||
Assert.AreEqual("Gilles", em); // Check the emailAddress parameter. | ||
}); | ||
|
||
var service = new CustomerService(smtpService.Object); | ||
|
||
service.SendMail("Gilles"); | ||
} | ||
``` | ||
|
||
### Remarks | ||
- If the parameters of mocked methods are very simple (primitive values for example), pass directly the expected value in the arguments of the method. For example | ||
```csharp | ||
smtpService.Setup(s => s.SendMail("[email protected]", "Gilles")) | ||
``` | ||
Instead of | ||
```csharp | ||
smtpService.Setup(s => s.SendMail("[email protected]", It.IsAny<string>())) | ||
``` | ||
- Use the `Callback()` to assert complex parameters. | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, use the `Callback()` method to check the `It.IsAny<T>()` arguments. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress a warning from this rule. Normally `It.IsAny<T>()` arguments should be check and asserted in the `Callback()` methods. |
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
104 changes: 104 additions & 0 deletions
104
src/Moq.Analyzers/Analyzers/CallBackDelegateParametersShouldNotBeIgnoredAnalyzer.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,104 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="CallBackDelegateParametersShouldNotBeIgnoredAnalyzer.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 CallBackDelegateParametersShouldNotBeIgnoredAnalyzer : DiagnosticAnalyzer | ||
{ | ||
internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( | ||
"PosInfoMoq1004", | ||
"The Callback() parameter should not be ignored if it has been setup as an It.IsAny<T>() argument", | ||
"The '{0}' parameter should not be ignored if it has been setup as an It.IsAny<T>() argument", | ||
"Design", | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
description: "The Callback() parameter should not be ignored if it has been setup as an It.IsAny<T>() argument.", | ||
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Design/PosInfoMoq1004.html"); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
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 moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel); | ||
|
||
// Extracts the Setup() linked method. | ||
var setupMethod = moqExpressionAnalyzer.ExtractSetupMethod(invocationExpression, context.CancellationToken); | ||
|
||
if (setupMethod is null) | ||
{ | ||
return; | ||
} | ||
|
||
// Check there is It.Any<T>() parameters. | ||
var itIsAnyArguments = new Dictionary<int, ChainInvocationArgument>(); | ||
|
||
for (var i = 0; i < setupMethod.InvocationArguments.Count; i++) | ||
{ | ||
var argument = setupMethod.InvocationArguments[i]; | ||
|
||
if (moqSymbols.IsItIsAny(argument.Symbol)) | ||
{ | ||
// The Callback() method is required for the argument, add in the list. | ||
itIsAnyArguments.Add(i, argument); | ||
} | ||
} | ||
|
||
if (itIsAnyArguments.Any()) | ||
{ | ||
// Here, it is mean Setup() method has been defined with some It.IsAny<T>() parameters. | ||
// Extracts the Callback() method (if exists). | ||
var callbackMethod = moqExpressionAnalyzer.ExtractCallBackLambdaExpressionMethod(invocationExpression, out var lambdaExpression, context.CancellationToken); | ||
|
||
if (callbackMethod is null) | ||
{ | ||
return; | ||
} | ||
|
||
// Check each parameter of the Callback() method. | ||
for (var i = 0; i < callbackMethod.Parameters.Length; i++) | ||
{ | ||
if (!itIsAnyArguments.TryGetValue(i, out var itIsAnyArgument)) | ||
{ | ||
// The parameter in the Callback() method is not related to a It.IsAny<T>() expression. | ||
continue; | ||
} | ||
|
||
if (callbackMethod.Parameters[i].Name == "_") | ||
{ | ||
// Raise warning for the parameter which is not used. | ||
var parameterName = setupMethod.InvocationArguments[i].ParameterSymbol.Name; | ||
|
||
context.ReportDiagnostic(Rule, lambdaExpression!.ParameterList.Parameters[i].GetLocation(), parameterName); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.