-
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.
* PosInfoMoq2012: The delegate in the argument of the Returns() method must return a value with same type of the mocked method. * PosInfoMoq2013: The delegate in the argument of the Returns()/ReturnsAsync() method must have the same parameter types of the mocked method/property.
- Loading branch information
1 parent
ba32035
commit dd853ba
Showing
10 changed files
with
744 additions
and
4 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 |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# PosInfoMoq2012: The delegate in the argument of the `Returns()` method must return a value with same type of the mocked method. | ||
|
||
| Property | Value | | ||
|-------------------------------------|-------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq2012 | | ||
| **Title** | The delegate in the argument of the `Returns()` method must return a value with same type of the mocked method. | | ||
| **Category** | Compilation | | ||
| **Default severity** | Error | | ||
|
||
## Cause | ||
|
||
The delegate in the argument of the `Returns()` must return return a value of the same type as the mocked method or property. | ||
|
||
## Rule description | ||
|
||
The lambda expression, anonymous method or method in the argument of the `Returns()` must return return a value of the same type as the mocked method or property. | ||
|
||
```csharp | ||
[Fact] | ||
public void Test() | ||
{ | ||
var validMock = new Mock<IService>(); | ||
validMock.Setup(s => s.GetData()) | ||
.Returns(() => | ||
{ | ||
return 1234; // OK, the mocked GetData() method return an int. | ||
}); | ||
validMock.Setup(s => s.IsAvailable) | ||
.Returns(() => | ||
{ | ||
return true; // OK, the mocked IsAvailable property return a bool. | ||
}); | ||
|
||
var invalidMock = new Mock<IService>(); | ||
invalidMock.Setup(s => s.GetData()) | ||
.Returns(() => | ||
{ | ||
return "Foobar"; // Error, the mocked GetData() method must return an int. | ||
}); | ||
invalidMock.Setup(s => s.IsAvailable) | ||
.Returns(() => | ||
{ | ||
return "Foobar"; // Error, the mocked IsAvailable property must return a bool. | ||
}); | ||
} | ||
|
||
public interface IService | ||
{ | ||
int GetData(); | ||
|
||
bool IsAvailable { get; } | ||
} | ||
``` | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, be sure the lambda expression, anonymous method or method as parameter of the `Returns()` | ||
method returns values with the same type of mocked method/property. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `ArgumentException` | ||
thrown with the *"Invalid callback. Setup on method with return type 'xxx' cannot invoke callback with return type 'yyy'."* message. |
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,69 @@ | ||
# PosInfoMoq2013: The delegate in the argument of the `Returns()`/`ReturnsAsync()` method must have the same parameter types of the mocked method/property. | ||
|
||
| Property | Value | | ||
|-------------------------------------|-------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq2013 | | ||
| **Title** | The delegate in the argument of the `Returns()`/`ReturnsAsync()` method must have the same parameter types of the mocked method/property. | | ||
| **Category** | Compilation | | ||
| **Default severity** | Error | | ||
|
||
## Cause | ||
|
||
The delegate in the argument of the `Returns()`/`ReturnsAsync()` method must have the same parameter types of the mocked method/property. | ||
|
||
## Rule description | ||
|
||
The lambda expression, anonymous method or method in the argument of the `Returns()` must have the same parameter types of the mocked method/property. | ||
> NB: Moq allows to pass a delegate with no argument in the `Returns()`/`ReturnsAsync()` method even the setup method contains arguments. | ||
```csharp | ||
[Fact] | ||
public void Test() | ||
{ | ||
var validMock = new Mock<IService>(); | ||
validMock.Setup(s => s.GetData(1234)) | ||
.Returns((int a) => // OK, the mocked GetData() take a int value as argument. | ||
{ | ||
return 1234; | ||
}); | ||
validMock.Setup(s => s.GetData(1234)) | ||
.Returns(() => // OK, Moq allows no arguments. | ||
{ | ||
return 1234; | ||
}); | ||
validMock.Setup(s => s.IsAvailable) // OK, property don't have arguments. | ||
.Returns(() => | ||
{ | ||
return true; | ||
}); | ||
|
||
var invalidMock = new Mock<IService>(); | ||
invalidMock.Setup(s => s.GetData(1234)) | ||
.Returns((string s) => // Error, the mocked GetData() take a int value as argument. | ||
{ | ||
return "Foobar"; | ||
}); | ||
invalidMock.Setup(s => s.IsAvailable) | ||
.Returns((string s) => // Error, mocked property have no arguments. | ||
{ | ||
return "Foobar"; | ||
}); | ||
} | ||
|
||
public interface IService | ||
{ | ||
int GetData(int id); | ||
|
||
bool IsAvailable { get; } | ||
} | ||
``` | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, be sure the lambda expression, anonymous method or method as parameter of the `Returns()`/`ReturnsAsync()` | ||
method must have the same arguments type of the setup property/method. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `ArgumentException` | ||
thrown with the *"Object of type 'xxx' cannot be converted to type 'yyy'."* message. |
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
145 changes: 145 additions & 0 deletions
145
src/Moq.Analyzers/Analyzers/ReturnsMethodDelegateMustMatchMockedMethodAnalyzer.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,145 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="ReturnsMethodDelegateMustMatchMockedMethodAnalyzer.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 ReturnsMethodDelegateMustMatchMockedMethodAnalyzer : DiagnosticAnalyzer | ||
{ | ||
internal static readonly DiagnosticDescriptor ReturnValueMustMatchRule = new DiagnosticDescriptor( | ||
"PosInfoMoq2012", | ||
"The delegate in the argument of the Returns() method must return a value with same type of the mocked method/property", | ||
"The delegate in the argument of the Returns() method must return a '{0}' type value", | ||
"Compilation", | ||
DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
description: "The delegate in the argument of the Returns() method must return a value with same type of the mocked method/property.", | ||
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2012.html"); | ||
|
||
private static readonly DiagnosticDescriptor ArgumentMustMatchRule = new DiagnosticDescriptor( | ||
"PosInfoMoq2013", | ||
"The delegate in the argument of the Returns()/ReturnsAsync() method must have the same parameter types of the mocked method/property", | ||
"The delegate in the argument of the Returns()/ReturnsAsync() method must have the same parameter types of the mocked method/property", | ||
"Compilation", | ||
DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
description: "The delegate in the argument of the Returns()/ReturnsAsync() method must have the same parameter types of the mocked method/property.", | ||
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2012.html"); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ReturnValueMustMatchRule, ArgumentMustMatchRule); | ||
|
||
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 invocationExpressionSymbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken); | ||
|
||
if (invocationExpressionSymbol.Symbol is not IMethodSymbol methodSymbol) | ||
{ | ||
return; | ||
} | ||
|
||
if (!moqSymbols.IsReturnsMethod(methodSymbol) && !moqSymbols.IsReturnsAsyncMethod(methodSymbol)) | ||
{ | ||
return; | ||
} | ||
|
||
// Gets the first argument of the Returns() method. | ||
if (invocationExpression.ArgumentList.Arguments.Count != 1) | ||
{ | ||
return; | ||
} | ||
|
||
var firstArgumentExpression = invocationExpression.ArgumentList.Arguments[0].Expression; | ||
|
||
if (firstArgumentExpression is not ParenthesizedLambdaExpressionSyntax delegateMethodSyntax) | ||
{ | ||
return; | ||
} | ||
|
||
var firstArgumentSymbol = context.SemanticModel.GetSymbolInfo(firstArgumentExpression, context.CancellationToken); | ||
|
||
if (firstArgumentSymbol.Symbol is not IMethodSymbol delegateMethodSymbol) | ||
{ | ||
return; | ||
} | ||
|
||
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel); | ||
|
||
// Extract the Setup() method. | ||
var setupMethod = moqExpressionAnalyzer.ExtractSetupMethod(invocationExpression, context.CancellationToken); | ||
|
||
if (setupMethod is null) | ||
{ | ||
return; | ||
} | ||
|
||
// Check the return type | ||
if (!moqSymbols.IsReturnsAsyncMethod(methodSymbol)) | ||
{ | ||
var expectedReturnType = setupMethod.ReturnType; | ||
|
||
if (!moqSymbols.IsAnyType(expectedReturnType) && !SymbolEqualityComparer.Default.Equals(delegateMethodSymbol.ReturnType, expectedReturnType)) | ||
{ | ||
context.ReportDiagnostic(ReturnValueMustMatchRule, firstArgumentExpression.GetLocation(), expectedReturnType.Name); | ||
} | ||
} | ||
|
||
// Check the argument types. | ||
if (setupMethod.IsProperty) | ||
{ | ||
if (delegateMethodSymbol.Parameters.Length > 0) | ||
{ | ||
// With property, the Returns() method must have no arguments. | ||
context.ReportDiagnostic(ArgumentMustMatchRule, delegateMethodSyntax.ParameterList.GetLocation()); | ||
} | ||
|
||
return; | ||
} | ||
|
||
if (delegateMethodSymbol.Parameters.Length == 0) | ||
{ | ||
// No argument in the delegate method, Moq accept it. | ||
return; | ||
} | ||
|
||
if (delegateMethodSymbol.Parameters.Length != setupMethod.InvocationArguments.Count) | ||
{ | ||
context.ReportDiagnostic(ArgumentMustMatchRule, delegateMethodSyntax.ParameterList.GetLocation()); | ||
return; | ||
} | ||
|
||
for (var i = 0; i < delegateMethodSymbol.Parameters.Length; i++) | ||
{ | ||
if (!SymbolEqualityComparer.Default.Equals(delegateMethodSymbol.Parameters[i].Type, setupMethod.InvocationArguments[i].ParameterSymbol.Type)) | ||
{ | ||
context.ReportDiagnostic(ArgumentMustMatchRule, delegateMethodSyntax.ParameterList.Parameters[i].GetLocation()); | ||
} | ||
} | ||
} | ||
} | ||
} |
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
Oops, something went wrong.