Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/github-actions-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
type: string
description: The version of the library
required: true
default: 1.13.0
default: 2.0.0
VersionSuffix:
type: string
description: The version suffix of the library (for example rc.1)
Expand Down
9 changes: 9 additions & 0 deletions PosInformatique.Moq.Analyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Design", "Design", "{815BE8
docs\Design\PosInfoMoq1007.md = docs\Design\PosInfoMoq1007.md
docs\Design\PosInfoMoq1008.md = docs\Design\PosInfoMoq1008.md
docs\Design\PosInfoMoq1009.md = docs\Design\PosInfoMoq1009.md
docs\Design\PosInfoMoq1010.md = docs\Design\PosInfoMoq1010.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation", "{D9C84D36-7F9C-4EFB-BE6F-9F7A05FE957D}"
Expand All @@ -59,6 +60,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation"
docs\Compilation\PosInfoMoq2014.md = docs\Compilation\PosInfoMoq2014.md
docs\Compilation\PosInfoMoq2015.md = docs\Compilation\PosInfoMoq2015.md
docs\Compilation\PosInfoMoq2016.md = docs\Compilation\PosInfoMoq2016.md
docs\Compilation\PosInfoMoq2017.md = docs\Compilation\PosInfoMoq2017.md
docs\Compilation\PosInfoMoq2018.md = docs\Compilation\PosInfoMoq2018.md
docs\Compilation\PosInfoMoq2019.md = docs\Compilation\PosInfoMoq2019.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Sandbox", "tests\Moq.Analyzers.Sandbox\Moq.Analyzers.Sandbox.csproj", "{07F970A1-1477-4D4C-B233-C9B4DA6E3AD6}"
Expand All @@ -71,6 +75,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\github-actions-release.yml = .github\workflows\github-actions-release.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
ProjectSection(SolutionItems) = preProject
src\.editorconfig = src\.editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ Design rules used to make your unit tests more strongly strict.
| [PosInfoMoq1006: The `It.IsAny<T>()` or `It.Is<T>()` arguments must match the parameters of the mocked method.](docs/Design/PosInfoMoq1006.md) | When setting up a method using `It.IsAny<T>()` or `It.Is<T>()` as arguments, the type `T` must exactly match the parameters of the configured method. |
| [PosInfoMoq1007: The `Verify()` method must specify the `Times` argument.](docs/Design/PosInfoMoq1007.md) | When calling the `Verify()` method, if the `Times` argument is not specified, Moq will assume `Times.AtLeastOnce()` by default. |
| [PosInfoMoq1008: The `Mock.Verify()` and `Mock.VerifyAll()` methods must specify at least one mock.](docs/Design/PosInfoMoq1008.md) | When calling the static methods `Mock.Verify()` or `Mock.VerifyAll()` without providing any `Mock<T>` instances, no verification is performed. |
| [PosInfoMoq1009: Avoid using `Verifiable()` method](docs/Design/PosInfoMoq1008.md) | A `Verify()` of an `Mock<T>` instance has not been called in the *Assert* phase of an unit test for `Verifiable()` setups. |
| [PosInfoMoq1009: Avoid using `Verifiable()` method](docs/Design/PosInfoMoq1009.md) | A `Verify()` of an `Mock<T>` instance has not been called in the *Assert* phase of an unit test for `Verifiable()` setups. |
| [PosInfoMoq1010: Use `+= null` syntax when raising events with `Raise()`/`RaiseAsync()`](docs/Design/PosInfoMoq1010.md) | When using `Mock<T>.Raise()` or `Mock<T>.RaiseAsync()`, the lambda expression used to identify the event should consistently use the `+= null` syntax. |

### Compilation

Expand All @@ -58,7 +59,7 @@ All the rules of this category should not be disabled (or changed their severity
| Rule | Description |
| - | - |
| [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. |
| [PosInfoMoq2001: The `Setup()`/`SetupSet()` method must be used only on overridable members](docs/Compilation/PosInfoMoq2001.md)) | The `Setup()` method must be applied only for overridable members. |
| [PosInfoMoq2001: The `Setup()`/`SetupSequence()`/`SetupSet()` method must be used only on overridable members](docs/Compilation/PosInfoMoq2001.md)) | The `Setup()`/`SetupSequence()`/`SetupSet()` method must be applied only for overridable members. |
| [PosInfoMoq2002: `Mock<T>` class can be used only to mock non-sealed class](docs/Compilation/PosInfoMoq2002.md) | The `Mock<T>` class can mock only interfaces or non-`sealed` classes. |
| [PosInfoMoq2003: The `Callback()` delegate expression must match the signature of the mocked method](docs/Compilation/PosInfoMoq2003.md) | The delegate in the argument of the `Callback()` method must match the signature of the mocked method. |
| [PosInfoMoq2004: Constructor arguments cannot be passed for interface mocks](docs/Compilation/PosInfoMoq2004.md) | No arguments can be passed to a mocked interface. |
Expand All @@ -74,6 +75,7 @@ All the rules of this category should not be disabled (or changed their severity
| [PosInfoMoq2014: The `Callback()` delegate expression must not return a value.](docs/Compilation/PosInfoMoq2014.md) | The `Callback()` delegate expression must not return a value. |
| [PosInfoMoq2015: The `Protected().Setup()` method must match the return type of the mocked method](docs/Compilation/PosInfoMoq2015.md) | The method setup with `Protected().Setup()` must match the return type of the mocked method. |
| [PosInfoMoq2016: `Mock<T>` constructor with factory lambda expression can be used only with classes.](docs/Compilation/PosInfoMoq2016.md) | The factory lambda expression used in `Mock<T>` instantiation must used only for the classes. |


| [PosInfoMoq2017: `Mock<T>.Raise()`/`RaiseAsync()` must use parameters matching the event signature.](docs/Compilation/PosInfoMoq2017.md) | The parameters passed to `Raise()` or `RaiseAsync()` must exactly match the parameters of the corresponding event delegate. |
| [PosInfoMoq2018: The first parameter of `Raise()`/`RaiseAsync()` must be an event.](docs/Compilation/PosInfoMoq2018.md) | The first parameter passed to `Raise()` or `RaiseAsync()` must reference an **event** member of the mocked type. |
| [PosInfoMoq2019: `RaiseAsync()` must be used only for events with async handlers (returning `Task`).](docs/Compilation/PosInfoMoq2019.md) | `Mock<T>.RaiseAsync()` must only be used with events whose delegate type returns `Task` (i.e., async events). |

12 changes: 6 additions & 6 deletions docs/Compilation/PosInfoMoq2001.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# PosInfoMoq2001: The `Setup()`/`SetupSet()` method must be used only on overridable members
# PosInfoMoq2001: The `Setup()`/`SetupSequence()`/`SetupSet()` method must be used only on overridable members

| Property | Value |
|-------------------------------------|---------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2001 |
| **Title** | The `Setup()`/`SetupSet()` method must be used only on overridable members |
| **Title** | The `Setup()`/`SetupSequence()`/`SetupSet()` method must be used only on overridable members |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

The `Setup()` or `SetupSet()` methods must be applied only for overridable members.
The `Setup()`, `SetupSequence()` or `SetupSet()` methods must be applied only for overridable members.
An overridable member is a **method** or **property** which is in:
- An `interface`.
- A non-`sealed` `class`. In this case, the member must be:
Expand All @@ -18,10 +18,10 @@ An overridable member is a **method** or **property** which is in:

## Rule description

The `Setup()` method must be applied only for overridable members.
The `Setup()`, `SetupSequence()` and `SetupSet()` methods must be applied only for overridable members.

For example:
- The following methods and properties can be mock and used in the `Setup()` method:
- The following methods and properties can be mock and used in the `Setup()` and `SetupSequence()` method:
- `IService.MethodCanBeMocked()`
- `IService.PropertyCanBeMocked`
- `Service.VirtualMethodCanBeMocked`
Expand Down Expand Up @@ -58,7 +58,7 @@ static methods which can not be overriden.

## How to fix violations

To fix a violation of this rule, be sure to mock a member in the `Setup()` or `SetupSet()` method which can be overriden.
To fix a violation of this rule, be sure to mock a member in the `Setup()`, `SetupSequence()` or `SetupSet()` method which can be overriden.

## When to suppress warnings

Expand Down
91 changes: 91 additions & 0 deletions docs/Compilation/PosInfoMoq2017.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# PosInfoMoq2017: `Mock<T>.Raise()`/`RaiseAsync()` must use parameters matching the event signature

| Property | Value |
|-----------------------|----------------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2017 |
| **Title** | `Mock<T>.Raise()`/`RaiseAsync()` must use parameters matching the event signature |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

The parameters passed to `Raise()` or `RaiseAsync()` must exactly match the parameters of the corresponding event delegate.

## Rule description

When mocking events with **Moq**, calling `Mock<T>.Raise()` or `Mock<T>.RaiseAsync()` requires that the provided arguments match the event signature.

- With the **`Raise(event, EventArgs)`** overload:
- Used for `EventHandler` or `EventHandler<T>`.
- Moq automatically supplies the `sender` (the mocked object).
- You only pass the `EventArgs` (or derived class) instance.

- With the **`Raise(event, params object[])`** overload:
- You provide all the arguments of the event delegate yourself.
- This includes the `sender` and all additional event arguments.

### Example

```csharp
public class Service
{
public event EventHandler Changed;
public event EventHandler<DataEventArgs> DataChanged;

public void DoSomething()
{
this.Changed?.Invoke(this, EventArgs.Empty);
this.DataChanged?.Invoke(this, new DataEventArgs(42));
}
}

public class DataEventArgs : EventArgs
{
public int Value { get; }
public DataEventArgs(int value) => Value = value;
}
```

#### Correct usage (matching parameters)

```csharp
var serviceMock = new Mock<Service>();

// Raise with EventHandler (sender is mocked object, EventArgs required)
serviceMock.Raise(s => s.Changed += null, EventArgs.Empty);

// Raise with EventHandler<T> (sender is mocked object, T required)
serviceMock.Raise(s => s.DataChanged += null, new DataEventArgs(42));

// Raise with params object[] (sender + event args explicitly)
serviceMock.Raise(s => s.DataChanged += null, "CustomSender", new DataEventArgs(42));
```

#### Incorrect usage (parameters not matching)

```csharp
var serviceMock = new Mock<Service>();

// Missing EventArgs
serviceMock.Raise(s => s.Changed += null); // ❌

// Wrong argument type
serviceMock.Raise(s => s.DataChanged += null, EventArgs.Empty); // ❌
```

## How to fix violations

To fix a violation, ensure that the arguments passed to `Raise()` or `RaiseAsync()` match **exactly** the event delegate signature:

- `EventHandler`: `(object sender, EventArgs e)`
- `EventHandler<T>`: `(object sender, T e)`

Depending on the overload:
- With `Raise(event, EventArgs)` → provide only `EventArgs` or `T`.
- With `Raise(event, params object[])` → provide both `sender` and `EventArgs` (or `T`).

## When to suppress warnings

Do not suppress errors from this rule. If skipped, Moq will throw a runtime exception such as `TargetParameterCountException`
(with the following message *"Parameter count mismatch"*) or an `ArgumentException` to explain that a value can not be converted
to an other type.
83 changes: 83 additions & 0 deletions docs/Compilation/PosInfoMoq2018.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# PosInfoMoq2018: The first parameter of `Raise()`/`RaiseAsync()` must be an event

| Property | Value |
|-----------------------|----------------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2018 |
| **Title** | The first parameter of `Raise()`/`RaiseAsync()` must be an event |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

The first parameter passed to `Raise()` or `RaiseAsync()` must reference an **event** member of the mocked type.
Using any other kind of member (property, method, field, etc.) is invalid.

## Rule description

When using Moq, `Mock<T>.Raise()` and `Mock<T>.RaiseAsync()` are designed to trigger **events** defined in the mocked type.
If the provided lambda expression instead references something that is not an event (like a property assignment), the code is invalid.

### Example

```csharp
public class Service
{
public event EventHandler Changed;
public event EventHandler<DataEventArgs> DataChanged;
public int Property { get; set; }
}

public class DataEventArgs : EventArgs
{
public int Value { get; }
public DataEventArgs(int value) => Value = value;
}
```

#### Correct usage (event targeted)

```csharp
var serviceMock = new Mock<Service>();

// Raise() with EventHandler
serviceMock.Raise(s => s.Changed += null, EventArgs.Empty);

// Raise() with EventHandler<T>
serviceMock.Raise(s => s.DataChanged += null, new DataEventArgs(42));

// RaiseAsync() with EventHandler<T>
await serviceMock.RaiseAsync(s => s.DataChanged += null, new DataEventArgs(42));
```

#### Incorrect usage (invalid target)

```csharp
var serviceMock = new Mock<Service>();

// Refers to a property, not an event
serviceMock.Raise(s => s.Property = 10, EventArgs.Empty); // ❌

// Same problem with RaiseAsync
await serviceMock.RaiseAsync(s => s.Property = 10, EventArgs.Empty); // ❌
```

## How to fix violations

Ensure that the first parameter of `Raise()` and `RaiseAsync()` always references an **event** of the mocked type, never a property or field.

Correct:

```csharp
serviceMock.Raise(s => s.Changed += null, EventArgs.Empty);
```

Incorrect:

```csharp
serviceMock.Raise(s => s.Property = 10, EventArgs.Empty);
```

## When to suppress warnings

Do not suppress this warning. If bypassed, Moq itself will throw an `ArgumentException` exception at runtime with a message such as
`Unsupported expression: m => (m.Property = 10)`.
85 changes: 85 additions & 0 deletions docs/Compilation/PosInfoMoq2019.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# PosInfoMoq2019: `RaiseAsync()` must be used only for events with async handlers (returning `Task`)

| Property | Value |
|-----------------------|----------------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2019 |
| **Title** | `RaiseAsync()` must be used only for events with async handlers (returning `Task`) |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

`Mock<T>.RaiseAsync()` must only be used with events whose delegate type returns `Task` (i.e., async events).
Using `RaiseAsync()` on events whose handlers return `void` (or any non-`Task` type) is invalid.

## Rule description

Moq provides `RaiseAsync()` to trigger asynchronous events. This requires that the event delegate returns `Task` (or `Task<T>`).
If the event delegate returns `void` (like `EventHandler` or `EventHandler<T>`), `RaiseAsync()` is not appropriate and will cause runtime issues.

### Delegate examples

```csharp
// Async event delegate
public delegate Task AsyncEventHandler(object sender, EventArgs e);

// Classic sync delegates (return void)
public delegate void VoidEventHandler(object sender, EventArgs e);
public class DataEventArgs : EventArgs { public int Value { get; } public DataEventArgs(int v) => Value = v; }
```

### Mocked type with both kinds of events

```csharp
public class Service
{
public event AsyncEventHandler AsyncChanged; // returns Task
public event EventHandler SyncChanged; // returns void
public event EventHandler<DataEventArgs> SyncDataChanged; // returns void
}
```

### Correct usage (async delegate returning Task)

```csharp
var serviceMock = new Mock<Service>();

// Use RaiseAsync with async event (Task-returning delegate)
await serviceMock.RaiseAsync(s => s.AsyncChanged += null, EventArgs.Empty);
```

### Incorrect usage (void-returning delegates)

```csharp
var serviceMock = new Mock<Service>();

// EventHandler returns void — using RaiseAsync is invalid
await serviceMock.RaiseAsync(s => s.SyncChanged += null, EventArgs.Empty); // ❌

// EventHandler<T> returns void — using RaiseAsync is invalid
await serviceMock.RaiseAsync(s => s.SyncDataChanged += null, new DataEventArgs(42)); // ❌
```

## How to fix violations

- If the event delegate returns `void`, use `Raise()` instead of `RaiseAsync()`.
- If you need async semantics, change the event to use a `Task`-returning delegate, for example `AsyncEventHandler`.

### Fix examples

```csharp
// Using Raise() for void-returning events
serviceMock.Raise(s => s.SyncChanged += null, EventArgs.Empty);
serviceMock.Raise(s => s.SyncDataChanged += null, new DataEventArgs(42));

// Using RaiseAsync() for Task-returning events
await serviceMock.RaiseAsync(s => s.AsyncChanged += null, EventArgs.Empty);
```

## When to suppress warnings

Do not suppress this rule.
If disabled, Moq may throw a runtime exception (`NullReferenceException`) due to a known bug when `RaiseAsync()` is used on non-async events,
instead of providing a clear error message.

Reference: https://github.com/devlooped/moq/issues/1568
Binary file added docs/Design/PosInfoMoq1000-Fixer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/Design/PosInfoMoq1000.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public void SendMail_ShouldCallSmtpService()
To fix a violation of this rule, call the `VerifyAll()` in the *Assert* phase
on the `Mock<T>` instances created during the *Arrange* phase.

### Visual Studio fixer
A Visual Studio fixer exists to add the call to the `VerifyAll()` method at the end of the unit test in the current document, project or solution.
![Visual Studio rule fixer](PosInfoMoq1000-Fixer.png)

## When to suppress warnings

Do not suppress a warning from this rule. Normally all setup methods must be call.
Loading