Skip to content

Commit fc643f3

Browse files
v1.7.0 (#25)
* Refactoring of the unit tests to use Moq library from NuGet packages (fixes #23). * Changes the PosInfoMoq1000 rule to check only VerifyAll() method call (#fixes #22). * Add the PosInfoMoq1002 rule (fixes #22). * Improving the PosInfoMoq2005 rules unit tests to check when no explicit constructors. * Improves the PosInfoMoq1002 rules to check the Verifiable() expression setup method depending of the expressions (#22). * Add unit test to check that custom Setup() method is not impacted by the analyzer (fixes #12).
1 parent da2f494 commit fc643f3

36 files changed

Lines changed: 1043 additions & 605 deletions

.github/workflows/github-actions-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
type: string
88
description: The version of the library
99
required: true
10-
default: 1.6.0
10+
default: 1.7.0
1111
VersionSuffix:
1212
type: string
1313
description: The version suffix of the library (for example rc.1)

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<!-- Add the default using directive for all the code -->
4444
<ItemGroup>
4545
<Using Include="System" />
46+
<Using Include="System.Threading.Tasks" />
4647
</ItemGroup>
4748

4849
</Project>

PosInformatique.Moq.Analyzers.sln

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ EndProject
2121
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DC2CDF77-88A9-490D-84ED-34832943104A}"
2222
ProjectSection(SolutionItems) = preProject
2323
tests\.editorconfig = tests\.editorconfig
24+
tests\Directory.Build.props = tests\Directory.Build.props
2425
EndProjectSection
2526
EndProject
2627
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{3C20D95F-AB5F-44EC-8FB6-CB9827B7FD63}"
@@ -29,6 +30,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Design", "Design", "{815BE8
2930
ProjectSection(SolutionItems) = preProject
3031
docs\design\PosInfoMoq1000.md = docs\design\PosInfoMoq1000.md
3132
docs\design\PosInfoMoq1001.md = docs\design\PosInfoMoq1001.md
33+
docs\Design\PosInfoMoq1002.md = docs\Design\PosInfoMoq1002.md
3234
EndProjectSection
3335
EndProject
3436
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation", "{D9C84D36-7F9C-4EFB-BE6F-9F7A05FE957D}"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ Design rules used to make your unit tests more strongly strict.
2626

2727
| Rule | Description |
2828
| - | - |
29-
| [PosInfoMoq1000: `Verify()` and `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances](docs/Design/PosInfoMoq1000.md) | When instantiating a `Mock<T>` in the *Arrange* phase of an unit test, `Verify()` or `VerifyAll()` method should be called in the *Assert* phase to check the setup methods has been called. |
29+
| [PosInfoMoq1000: `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances](docs/Design/PosInfoMoq1000.md) | When instantiating a `Mock<T>` in the *Arrange* phase of an unit test, `VerifyAll()` method should be called in the *Assert* phase to check the setup methods has been called. |
3030
| [PosInfoMoq1001: The `Mock<T>` instance behavior should be defined to Strict mode](docs/Design/PosInfoMoq1001.md) | When instantiating a `Mock<T>` instance, the `MockBehavior` of the `Mock` instance should be defined to `Strict`. |
31-
31+
| [PosInfoMoq1002: `Verify()` methods should be called when `Verifiable()` has been setup](docs/Design/PosInfoMoq1002.md) | When a mocked member has been setup with the `Verifiable()` method, the `Verify()` method must be called at the end of the unit test. |
3232

3333
### Compilation
3434

docs/Design/PosInfoMoq1000.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
# PosInfoMoq1000: `Verify()` and `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances
1+
# PosInfoMoq1000: ``VerifyAll()` method should be called when instantiate a `Mock<T>` instances
22

33
| Property | Value |
44
|-------------------------------------|--------------------------------------------------------------------------------------------|
5-
| **Rule ID** | PosInfoMoq1000 |
6-
| **Title** | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances |
5+
| **Rule ID** | PosInfoMoq1000 |
6+
| **Title** | `VerifyAll()` methods should be called when instantiate a Mock<T> instances |
77
| **Category** | Design |
88
| **Default severity** | Warning |
99

1010
## Cause
1111

12-
A `Verify()` or `VerifyAll()` of an `Mock<T>` instance has not been called in the *Assert* phase
12+
A `VerifyAll()` of an `Mock<T>` instance has not been called in the *Assert* phase
1313
of an unit test.
1414

1515
## Rule description
1616

17-
When instantiating a `Mock<T>` in the *Arrange* phase of an unit test, `Verify()` or `VerifyAll()` method
17+
When instantiating a `Mock<T>` in the *Arrange* phase of an unit test, `VerifyAll()` method
1818
should be called in the *Assert* phase to check the setup methods has been called.
1919

2020
```csharp
@@ -37,7 +37,7 @@ public void GetCustomer_ShouldCallRepository()
3737

3838
## How to fix violations
3939

40-
To fix a violation of this rule, call the `Verify()` or `VerifyAll()` in the *Assert* phase
40+
To fix a violation of this rule, call the `VerifyAll()` in the *Assert* phase
4141
on the `Mock<T>` instances created during the *Arrange* phase.
4242

4343
## When to suppress warnings

docs/Design/PosInfoMoq1002.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# PosInfoMoq1002: `Verify()` methods should be called when `Verifiable()` has been setup
2+
3+
| Property | Value |
4+
|-------------------------------------|------------------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq1002 |
6+
| **Title** | `Verify()` methods should be called when `Verifiable()` has been setup |
7+
| **Category** | Design |
8+
| **Default severity** | Warning |
9+
10+
## Cause
11+
12+
A `Verify()` of an `Mock<T>` instance has not been called in the *Assert* phase
13+
of an unit test for `Verifiable()` setups.
14+
15+
## Rule description
16+
17+
In the *Arrange* phase of an unit test, when `Verifiable()` method has been setup to mocked member, the
18+
`Verify()` method should be called in the *Assert* phase to check the setup member has been called.
19+
20+
```csharp
21+
[Fact]
22+
public void GetCustomer_ShouldCallRepository()
23+
{
24+
// Arrange
25+
var smtpService = new Mock<ISmtpService>();
26+
smtpService.Setup(s => s.SendMail("sender@domain.com", "Gilles"))
27+
.Verifiable();
28+
29+
var service = new CustomerService(repository.Object);
30+
31+
// Act
32+
service.SendMail("Gilles");
33+
34+
// Assert
35+
smtpService.Verify(); // The Verify() will check that the mocked ISmtpService.SendMail() has been called (because marked with the ".Verifiable()" method).
36+
}
37+
```
38+
39+
## How to fix violations
40+
41+
To fix a violation of this rule, call the `Verify()` in the *Assert* phase
42+
on the `Mock<T>` instances, if some mocked members has been marked as `Verifiable()` in the *Arrange* phase.
43+
44+
## When to suppress warnings
45+
46+
Do not suppress a warning from this rule. Normally `Verifiable()` setup members must be call in the unit tests execution.

src/Moq.Analyzers/AnalyzerReleases.Shipped.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
## Release 1.6.0
1+
## Release 1.7.0
2+
3+
### New Rules
4+
5+
Rule ID | Category | Severity | Notes
6+
--------|----------|----------|--------------------
7+
PosInfoMoq1002 | Design | Warning | `Verify()` methods should be called when `Verifiable()` has been setup.
8+
9+
## Release 1.6.0
210

311
### New Rules
412

@@ -48,5 +56,5 @@ PosInfoMoq2000 | Compilation | Error | The `Returns()` or `ReturnsAsync()` met
4856

4957
Rule ID | Category | Severity | Notes
5058
--------|----------|----------|--------------------
51-
PosInfoMoq1000 | Design | Warning | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances.
59+
PosInfoMoq1000 | Design | Warning | `VerifyAll()` method should be called when instantiate a Mock<T> instances.
5260
PosInfoMoq1001 | Design | Warning | The `Mock<T>` instance behavior should be defined to Strict mode.

src/Moq.Analyzers/Analyzers/CallBackDelegateMustMatchMockedMethodAnalyzer.cs

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -64,58 +64,46 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
6464
return;
6565
}
6666

67-
// Check each CallBack() method for the following calls.
68-
var followingMethods = invocationExpression.DescendantNodes().OfType<InvocationExpressionSyntax>();
67+
// Extracts the setup method from the Callback() method call.
68+
var setupMethod = moqExpressionAnalyzer.ExtractSetupMethod(invocationExpression, context.CancellationToken);
6969

70-
foreach (var followingMethod in followingMethods)
70+
if (setupMethod is null)
7171
{
72-
// Find the symbol of the mocked method (if not symbol found, it is mean we Setup() method that not currently compile)
73-
// so we skip the analysis.
74-
if (!moqExpressionAnalyzer.IsMockSetupMethod(followingMethod, out var _, context.CancellationToken))
75-
{
76-
continue;
77-
}
78-
79-
var mockedMethod = moqExpressionAnalyzer.ExtractSetupMethod(followingMethod, out var _, context.CancellationToken);
80-
81-
if (mockedMethod is null)
82-
{
83-
continue;
84-
}
72+
return;
73+
}
8574

86-
// Compare the parameters between the mocked method and lambda expression in the CallBack() method.
87-
// 1- Compare the number of the parameters
88-
if (callBackLambdaExpressionSymbol.Parameters.Length != mockedMethod.Parameters.Length)
89-
{
90-
var diagnostic = Diagnostic.Create(Rule, lambdaExpression!.ParameterList.GetLocation());
91-
context.ReportDiagnostic(diagnostic);
75+
// Compare the parameters between the mocked method and lambda expression in the CallBack() method.
76+
// 1- Compare the number of the parameters
77+
if (callBackLambdaExpressionSymbol.Parameters.Length != setupMethod.InvocationArguments.Count)
78+
{
79+
var diagnostic = Diagnostic.Create(Rule, lambdaExpression!.ParameterList.GetLocation());
80+
context.ReportDiagnostic(diagnostic);
9281

93-
continue;
94-
}
82+
return;
83+
}
9584

96-
// 2- Iterate for each parameter
97-
for (var i = 0; i < callBackLambdaExpressionSymbol.Parameters.Length; i++)
85+
// 2- Iterate for each parameter
86+
for (var i = 0; i < callBackLambdaExpressionSymbol.Parameters.Length; i++)
87+
{
88+
// Special case, if the argument is IsAnyType
89+
if (moqSymbols.IsAnyType(setupMethod.InvocationArguments[i].ParameterSymbol.Type))
9890
{
99-
// Special case, if the argument is IsAnyType
100-
if (moqSymbols.IsAnyType(mockedMethod.Parameters[i].Type))
101-
{
102-
// The callback parameter associated must be an object.
103-
if (callBackLambdaExpressionSymbol.Parameters[i].Type.SpecialType != SpecialType.System_Object)
104-
{
105-
var diagnostic = Diagnostic.Create(Rule, lambdaExpression!.ParameterList.Parameters[i].GetLocation());
106-
context.ReportDiagnostic(diagnostic);
107-
108-
continue;
109-
}
110-
}
111-
else if (!SymbolEqualityComparer.Default.Equals(callBackLambdaExpressionSymbol.Parameters[i].Type, mockedMethod.Parameters[i].Type))
91+
// The callback parameter associated must be an object.
92+
if (callBackLambdaExpressionSymbol.Parameters[i].Type.SpecialType != SpecialType.System_Object)
11293
{
11394
var diagnostic = Diagnostic.Create(Rule, lambdaExpression!.ParameterList.Parameters[i].GetLocation());
11495
context.ReportDiagnostic(diagnostic);
11596

11697
continue;
11798
}
11899
}
100+
else if (!SymbolEqualityComparer.Default.Equals(callBackLambdaExpressionSymbol.Parameters[i].Type, setupMethod.InvocationArguments[i].ParameterSymbol.Type))
101+
{
102+
var diagnostic = Diagnostic.Create(Rule, lambdaExpression!.ParameterList.Parameters[i].GetLocation());
103+
context.ReportDiagnostic(diagnostic);
104+
105+
continue;
106+
}
119107
}
120108
}
121109
}

src/Moq.Analyzers/Analyzers/SetupMustBeUsedOnlyForOverridableMembersAnalyzer.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
5757
}
5858

5959
// Extracts the method in the lambda expression of the Setup() method
60-
var members = moqExpressionAnalyzer.ExtractChainedMembersInvocationFromLambdaExpression(invocationExpression, context.CancellationToken);
60+
var setupMethod = moqExpressionAnalyzer.ExtractChainedMembersInvocationFromLambdaExpression(invocationExpression, context.CancellationToken);
61+
62+
if (setupMethod is null)
63+
{
64+
return;
65+
}
6166

6267
// Check if the member is overridable.
63-
foreach (var member in members)
68+
foreach (var member in setupMethod.Members)
6469
{
6570
if (!moqSymbols.IsOverridable(member.Symbol))
6671
{

src/Moq.Analyzers/Analyzers/VerifyShouldBeCalledAnalyzer.cs renamed to src/Moq.Analyzers/Analyzers/VerifyAllShouldBeCalledAnalyzer.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//-----------------------------------------------------------------------
2-
// <copyright file="VerifyShouldBeCalledAnalyzer.cs" company="P.O.S Informatique">
2+
// <copyright file="VerifyAllShouldBeCalledAnalyzer.cs" company="P.O.S Informatique">
33
// Copyright (c) P.O.S Informatique. All rights reserved.
44
// </copyright>
55
//-----------------------------------------------------------------------
@@ -13,16 +13,16 @@ namespace PosInformatique.Moq.Analyzers
1313
using Microsoft.CodeAnalysis.Diagnostics;
1414

1515
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16-
public class VerifyShouldBeCalledAnalyzer : DiagnosticAnalyzer
16+
public class VerifyAllShouldBeCalledAnalyzer : DiagnosticAnalyzer
1717
{
1818
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
1919
"PosInfoMoq1000",
20-
"Verify() and VerifyAll() methods should be called when instantiate a Mock<T> instances",
21-
"The Verify() or VerifyAll() method should be called at the end of the unit test",
20+
"VerifyAll() method should be called when instantiate a Mock<T> instances",
21+
"The VerifyAll() method should be called at the end of the unit test",
2222
"Design",
2323
DiagnosticSeverity.Warning,
2424
isEnabledByDefault: true,
25-
description: "VerifyAll() or VerifyAll() methods should be called at the end of the unit test method.",
25+
description: "VerifyAll() method should be called at the end of the unit test method.",
2626
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Design/PosInfoMoq1000.html");
2727

2828
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
@@ -105,14 +105,14 @@ private static bool IsMockVerifyAllInvocation(InvocationExpressionSyntax invocat
105105
return false;
106106
}
107107

108-
if (!moqSymbols.IsVerifyMethod(verifyMethod.Symbol) && !moqSymbols.IsVerifyAllMethod(verifyMethod.Symbol))
108+
if (!moqSymbols.IsVerifyAllMethod(verifyMethod.Symbol))
109109
{
110-
if (!moqSymbols.IsVerifyStaticMethod(verifyMethod.Symbol) && !moqSymbols.IsVerifyAllStaticMethod(verifyMethod.Symbol))
110+
if (!moqSymbols.IsVerifyAllStaticMethod(verifyMethod.Symbol))
111111
{
112112
return false;
113113
}
114114

115-
// Special case, the static method Verify() or VerifyAll() has been called.
115+
// Special case, the static method VerifyAll() has been called.
116116
// In this case, iterate on each arguments of the method called and check if the variableNameSymbol has been passed.
117117
foreach (var argument in invocation.ArgumentList.Arguments)
118118
{

0 commit comments

Comments
 (0)