Skip to content

Commit 14f68a6

Browse files
v1.1.0 (#2)
* Add the MQ2000 rules to check that Returns() methods are called with Strict behavior mocks. * Change the DiagnosticId with the **PosInfoMoq** prefix to avoid conflicts with other Analyzers.
1 parent 2e422ab commit 14f68a6

19 files changed

Lines changed: 1038 additions & 228 deletions

PosInformatique.Moq.Analyzers.sln

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DC2CDF77
3131
EndProject
3232
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{3C20D95F-AB5F-44EC-8FB6-CB9827B7FD63}"
3333
EndProject
34-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "design", "design", "{815BE8D0-C7D5-4B4E-82E0-DE29C11F258E}"
34+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Design", "Design", "{815BE8D0-C7D5-4B4E-82E0-DE29C11F258E}"
3535
ProjectSection(SolutionItems) = preProject
36-
docs\design\MQ1000.md = docs\design\MQ1000.md
37-
docs\design\MQ1001.md = docs\design\MQ1001.md
36+
docs\design\PosInfoMoq1001.md = docs\design\PosInfoMoq1001.md
37+
docs\design\PosInfoMoq1000.md = docs\design\PosInfoMoq1000.md
38+
EndProjectSection
39+
EndProject
40+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation", "{D9C84D36-7F9C-4EFB-BE6F-9F7A05FE957D}"
41+
ProjectSection(SolutionItems) = preProject
42+
docs\Compilation\PosInfoMoq2000.md = docs\Compilation\PosInfoMoq2000.md
3843
EndProjectSection
3944
EndProject
4045
Global
@@ -60,6 +65,7 @@ Global
6065
{DC2CDF77-88A9-490D-84ED-34832943104A} = {1D59B801-B4D3-44FC-A2BE-F2F53AC54061}
6166
{3C20D95F-AB5F-44EC-8FB6-CB9827B7FD63} = {1D59B801-B4D3-44FC-A2BE-F2F53AC54061}
6267
{815BE8D0-C7D5-4B4E-82E0-DE29C11F258E} = {3C20D95F-AB5F-44EC-8FB6-CB9827B7FD63}
68+
{D9C84D36-7F9C-4EFB-BE6F-9F7A05FE957D} = {3C20D95F-AB5F-44EC-8FB6-CB9827B7FD63}
6369
EndGlobalSection
6470
GlobalSection(ExtensibilityGlobals) = postSolution
6571
SolutionGuid = {3307E7F7-9CD7-4C12-B34A-943F5A8B62A4}

README.md

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

2727
| Rule | Description |
2828
| - | - |
29-
| [MQ1000: `Verify()` and `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances](docs/design/MQ1000.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. |
30-
| [MQ1001: The `Mock<T>` instance behavior should be defined to Strict mode](docs/design/MQ1001.md) | When instantiating a `Mock<T>` instance, the `MockBehavior` of the `Mock` instance should be defined to `Strict`. |
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. |
30+
| [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+
32+
33+
### Compilation
34+
35+
Compilation rules check some error during the compilation to be sure that the execution of the unit tests with `Mock<T>` will not raise exceptions.
36+
All the rules of this category should not be disabled (or changed their severity differently of **Error**).
37+
38+
| Rule | Description |
39+
| - | - |
40+
| [PosInfoMoq2000: The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks](docs/Compilation/PosInfoMoq2000.md) | When a `Mock<T>` has been defined with the `Strict` behavior, the `Returns()` or `ReturnsAsync()` method must be called when setup a method to mock which returns a value. |

build/azure-pipelines-release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ parameters:
22
- name: VersionPrefix
33
displayName: The version of the library
44
type: string
5-
default: 1.0.0
5+
default: 1.1.0
66
- name: VersionSuffix
77
displayName: The version suffix of the library (rc.1). Use a space ' ' if no suffix.
88
type: string

docs/Compilation/PosInfoMoq2000.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# PosInfoMoq2000: The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks
2+
3+
| Property | Value |
4+
|-------------------------------------|--------------------------------------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2000 |
6+
| **Title** | The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
9+
10+
## Cause
11+
12+
A `Returns()` or `ReturnsAsync()` of an `Mock<T>` instance with `Strict` behavior has not been called after a `Setup()` call.
13+
14+
## Rule description
15+
16+
When a `Mock<T>` has been defined with the `Strict` behavior, the `Returns()` or `ReturnsAsync()` methods must be call
17+
when setup a method to mock which returns a value.
18+
19+
```csharp
20+
[Fact]
21+
public void GetCustomer_ShouldCallRepository()
22+
{
23+
// Arrange
24+
repository = new Mock<IRepository>(MockBehavior.Strict);
25+
repository.Setup(r => r.GetData()); // No Returns() method has been specified.
26+
...
27+
...
28+
// A MoqException will be thrown when the GetData() method will be called.
29+
}
30+
```
31+
32+
## How to fix violations
33+
34+
To fix a violation of this rule, call the `Returns()` or `ReturnsAsync()` method after the `Setup()`
35+
call to setup the method to mock.
36+
37+
For example with the following code:
38+
39+
```csharp
40+
public interface IRepository
41+
{
42+
int GetData();
43+
}
44+
45+
public class CustomerService
46+
{
47+
private readonly IRepository repository;
48+
49+
public CustomerService(IRepository repository)
50+
{
51+
this.repository = repository;
52+
}
53+
54+
public int GetDataFromRepository()
55+
{
56+
return this.repository.GetData();
57+
}
58+
}
59+
```
60+
61+
For the associated unit test, the `Returns()` method have to be called for the `GetData()` method setup.
62+
63+
```csharp
64+
[Fact]
65+
public void GetCustomer_ShouldCallRepository()
66+
{
67+
// Arrange
68+
repository = new Mock<IRepository>(MockBehavior.Strict);
69+
repository.Setup(r => r.GetData())
70+
.Returns(1234); // The Returns() method is mandatory.
71+
72+
var service = new CustomerService(repository.Object);
73+
74+
// Act
75+
var result = service.GetDataFromRepository();
76+
77+
// Arrange
78+
result.Should().Be(1234);
79+
}
80+
```
81+
82+
## When to suppress warnings
83+
84+
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `MoqException`
85+
thrown with the *"Invocation needs to return a value and therefore must have a corresponding setup that provides it."* message.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# MQ1000: `Verify()` and `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances
1+
# PosInfoMoq1000: `Verify()` and `VerifyAll()` methods should be called when instantiate a `Mock<T>` instances
22

33
| Property | Value |
44
|-------------------------------------|--------------------------------------------------------------------------------------------|
5-
| **Rule ID** | MQ1000 |
5+
| **Rule ID** | PosInfoMoq1000 |
66
| **Title** | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances |
77
| **Category** | Design |
88
| **Default severity** | Warning |
@@ -30,7 +30,7 @@ public void GetCustomer_ShouldCallRepository()
3030
// Act
3131
service.SendMail("Gilles");
3232

33-
// Arrange
33+
// Assert
3434
smtpService.VerifyAll(); // The VerifyAll() will check that the mocked ISmtpService.SendMail() has been called.
3535
}
3636
```
@@ -42,4 +42,4 @@ on the `Mock<T>` instances created during the *Arrange* phase.
4242

4343
## When to suppress warnings
4444

45-
Do not suppress a warning from this rule. Normally all setup methods must be called.
45+
Do not suppress a warning from this rule. Normally all setup methods must be call.
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# MQ1001: The `Mock<T>` instance behavior should be defined to Strict mode
1+
# PosInfoMoq1001: The `Mock<T>` instance behavior should be defined to Strict mode
22

33
| Property | Value |
44
|-------------------------------------|------------------------------------------------------------------|
5-
| **Rule ID** | MQ1001 |
5+
| **Rule ID** | PosInfoMoq1001 |
66
| **Title** | The `Mock<T>` instance behavior should be defined to Strict mode |
77
| **Category** | Design |
88
| **Default severity** | Warning |
@@ -24,11 +24,11 @@ public interface IRepository
2424
int GetData();
2525
}
2626

27-
public class Service
27+
public class CustomerService
2828
{
2929
private readonly IRepository repository;
3030

31-
public Service(IRepository repository)
31+
public CustomerService(IRepository repository)
3232
{
3333
this.repository = repository;
3434
}
@@ -48,9 +48,9 @@ the `GetData()` method has not been setup, the default `int` value (`0`) will be
4848
public void GetDataFromRepository()
4949
{
5050
// Arrange
51-
var repository = new Mock<ISmtpService>(); // Default behavior (Loose)
51+
var repository = new Mock<IRepository>(); // Default behavior (Loose)
5252
53-
// /!\ No methods on the ISmtpService has been setup !
53+
// /!\ No methods on the IRepository has been setup !
5454
5555
var service = new CustomerService(repository.Object);
5656

@@ -67,9 +67,9 @@ set the `MockBehavior` to `Strict` in the constructor of the `Mock<T>` class.
6767
public void GetDataFromRepository()
6868
{
6969
// Arrange
70-
var repository = new Mock<ISmtpService>(MockBehavior.Strict); // Strict behavior (Loose)
70+
var repository = new Mock<IRepository>(MockBehavior.Strict); // Strict behavior (Loose)
7171
72-
// /!\ No methods on the ISmtpService has been setup !
72+
// /!\ No methods on the IRepository has been setup !
7373
7474
var service = new CustomerService(repository.Object);
7575

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
## Release 1.0.0
1+
## Release 1.1.0
22

33
### New Rules
44

55
Rule ID | Category | Severity | Notes
66
--------|----------|----------|--------------------
7-
MQ1000 | Design | Warning | Verify() and VerifyAll() methods should be called when instantiate a Mock<T> instances
8-
MQ1001 | Design | Warning | The `Mock<T>` instance behavior should be defined to Strict mode
7+
PosInfoMoq2000 | Compilation | Error | The `Returns()` or `ReturnsAsync()` method must be called for Strict mocks
8+
9+
## Release 1.0.0
10+
11+
### New Rules
12+
13+
Rule ID | Category | Severity | Notes
14+
--------|----------|----------|--------------------
15+
PosInfoMoq1000 | Design | Warning | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances
16+
PosInfoMoq1001 | Design | Warning | The `Mock<T>` instance behavior should be defined to Strict mode

src/Moq.Analyzers/Analyzers/MockInstanceShouldBeStrictBehaviorAnalyzer.cs

Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace PosInformatique.Moq.Analyzers
1515
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1616
public class MockInstanceShouldBeStrictBehaviorAnalyzer : DiagnosticAnalyzer
1717
{
18-
public const string DiagnosticId = "MQ1001";
18+
public const string DiagnosticId = "PosInfoMoq1001";
1919

2020
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
2121
DiagnosticId,
@@ -40,79 +40,23 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
4040
{
4141
var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
4242

43-
// Check there is "new Mock<I>()" statement.
44-
if (!MockExpressionHelper.IsMockCreation(objectCreation))
45-
{
46-
return;
47-
}
43+
var moqSymbols = MoqSymbols.FromCompilation(context.Compilation);
4844

49-
// Check that the "new Mock<I>()" statement have at least one argument (else Strict is missing...).
50-
if (objectCreation.ArgumentList is null)
45+
if (moqSymbols is null)
5146
{
52-
var diagnostic = Diagnostic.Create(Rule, objectCreation.GetLocation());
53-
context.ReportDiagnostic(diagnostic);
54-
5547
return;
5648
}
5749

58-
var firstArgument = objectCreation.ArgumentList.Arguments.FirstOrDefault();
59-
60-
if (firstArgument is null)
50+
// Check there is "new Mock<I>()" statement.
51+
if (!MockExpressionHelper.IsMockCreation(moqSymbols, context.SemanticModel, objectCreation))
6152
{
62-
var diagnostic = Diagnostic.Create(Rule, objectCreation.GetLocation());
63-
context.ReportDiagnostic(diagnostic);
64-
6553
return;
6654
}
6755

68-
// Gets the first argument of "new Mock<I>(...)" and ensures it is a MemberAccessExpressionSyntax
69-
// (because we searching for MockBehavior.Strict).
70-
if (firstArgument.Expression is not MemberAccessExpressionSyntax memberAccessExpression)
56+
if (!MockExpressionHelper.IsStrictBehavior(moqSymbols, context.SemanticModel, objectCreation))
7157
{
7258
var diagnostic = Diagnostic.Create(Rule, objectCreation.GetLocation());
7359
context.ReportDiagnostic(diagnostic);
74-
75-
return;
76-
}
77-
78-
// Find the "MockBehavior" type in the semantic model from Moq library.
79-
var mockBehaviorType = context.Compilation.GetTypeByMetadataName("Moq.MockBehavior");
80-
81-
if (mockBehaviorType is null)
82-
{
83-
// Moq not installed (dependency of the Moq package missing), so we stop analysis.
84-
return;
85-
}
86-
87-
// Check that the "memberAccessExpression.Expression" is applied on the Moq MockBehavior type.
88-
var firstArgumentType = context.SemanticModel.GetSymbolInfo(memberAccessExpression.Expression);
89-
90-
if (!SymbolEqualityComparer.Default.Equals(firstArgumentType.Symbol, mockBehaviorType))
91-
{
92-
var diagnostic = Diagnostic.Create(Rule, firstArgument.GetLocation());
93-
context.ReportDiagnostic(diagnostic);
94-
95-
return;
96-
}
97-
98-
// Find the Strict field in the semantic model
99-
var strictField = mockBehaviorType.GetMembers("Strict").SingleOrDefault();
100-
101-
if (strictField is null)
102-
{
103-
// The field Strict seam to not exists (dependency of the Moq package missing ? Or something wrong ?), so we stop analysis.
104-
return;
105-
}
106-
107-
// Check that the memberAccessExpression.Name reference the Strict field
108-
var firstArgumentField = context.SemanticModel.GetSymbolInfo(memberAccessExpression.Name);
109-
110-
if (!SymbolEqualityComparer.Default.Equals(firstArgumentField.Symbol, strictField))
111-
{
112-
var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.Name.GetLocation());
113-
context.ReportDiagnostic(diagnostic);
114-
115-
return;
11660
}
11761
}
11862
}

0 commit comments

Comments
 (0)