Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up README and add docs for each analyzer rule #77

Merged
merged 7 commits into from
Jun 10, 2024
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
6 changes: 6 additions & 0 deletions CODE-OF-CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant
to clarify expected behavior in our community.

For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Contributing

We welcome contributions. If you want to contribute to existing issues, check the
[help wanted](https://github.com/rjmurillo/moq.analyzers/labels/help%20wanted) or
[good first issue](https://github.com/rjmurillo/moq.analyzers/labels/good%20first%20issue) items in the backlog.

If you have new ideas or want to complain about bugs, feel free to [create a new issue](https://github.com/rjmurillo/moq.analyzers/issues/new).
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
# Moq.Analyzers

**Moq.Analyzers** is a Roslyn analyzer that helps to write unit tests using the popular and friend [Moq](https://github.com/devlooped/moq) library. Moq.Analyzers protects you from popular mistakes and warns you if something is wrong with your Moq configuration:
[![NuGet Version](https://img.shields.io/nuget/v/Moq.Analyzers?style=flat&logo=nuget&color=blue)](https://www.nuget.org/packages/Moq.Analyzers)
[![NuGet Downloads](https://img.shields.io/nuget/dt/Moq.Analyzers?style=flat&logo=nuget)](https://www.nuget.org/packages/Moq.Analyzers)
[![Main build](https://github.com/rjmurillo/moq.analyzers/actions/workflows/main.yml/badge.svg)](https://github.com/rjmurillo/moq.analyzers/actions/workflows/main.yml)

## Detected issues
**Moq.Analyzers** is a Roslyn analyzer that helps you to write unit tests using the popular
[Moq](https://github.com/devlooped/moq) framework. Moq.Analyzers protects you from common mistakes and warns you if
something is wrong with your Moq configuration.

* Moq1000 = Sealed classes cannot be mocked.
* Moq1001 = Mocked interfaces cannot have constructor parameters.
* Moq1002 = Parameters provided into mock do not match any existing constructors.
* Moq1100 = Callback signature must match the signature of the mocked method.
* Moq1101 = SetupGet/SetupSet should be used for properties, not for methods.
* Moq1200 = Setup should be used only for overridable members.
* Moq1201 = Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`.
* Moq1300 = Mock.As() should take interfaces.
## Analyzer rules

## How to install
* Moq1000: Sealed classes cannot be mocked
* Moq1001: Mocked interfaces cannot have constructor parameters
* Moq1002: Parameters provided into mock do not match any existing constructors
* Moq1100: Callback signature must match the signature of the mocked method
* Moq1101: SetupGet/SetupSet should be used for properties, not for methods
* Moq1200: Setup should be used only for overridable members
* Moq1201: Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`
* Moq1300: Mock.As() should take interfaces

Install ["Moq.Analyzers" NuGet package](https://www.nuget.org/packages/Moq.Analyzers) into test projects using Moq.
See [docs/rules](./docs/rules/README.md) for full documentation.

You must use an in-support version of the .NET SDK (i.e. 6+).
## Getting started

## Contributions are welcome!
Moq.Analyzers is installed from NuGet. Run this command for your test project(s):

Moq.Analyzers continues to evolve and add new features. Any help will be appreciated. You can report issues, develop new features, improve the documention, or do other cool stuff.
```powershell
dotnet add package Moq.Analyzers
```

If you want to contribute to existing issues, check the [help wanted](https://github.com/rjmurillo/moq.analyzers/labels/help%20wanted) or [good first issue](https://github.com/rjmurillo/moq.analyzers/labels/good%20first%20issue) items in the backlog. If you have new ideas or want to complain about bugs, feel free to [create a new issue](https://github.com/rjmurillo/moq.analyzers/issues/new).
> NOTE: You must use a [supported version](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core) of
> the .NET SDK (i.e. 6.0 or later).

## Code of Conduct
## Contributions welcome

This project has adopted the code of conduct defined by the [Contributor Covenant](https://www.contributor-covenant.org/) to set expectations for behavior in our communication. For more information, see the [.NET Foundation's Contributor Convenant Code of Conduct](https://dotnetfoundation.org/about/policies/code-of-conduct)
Moq.Analyzers continues to evolve and add new features. Any help will be appreciated. You can report issues,
develop new features, improve the documentation, or do other cool stuff. See [CONTRIBUTING.md](./CONTRIBUTING.md).
30 changes: 30 additions & 0 deletions docs/rules/Moq1000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Moq1000: Sealed classes cannot be mocked

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
---

Mocking requires generating a subclass of the class to be mocked. Sealed classes cannot be subclassed. To fix:

- Introduce an interface and mock that instead
- Use the real class and not a mock
- Unseal the class

## Examples of patterns that are flagged by this analyzer

```csharp
sealed class MyClass { }

var mock = new Mock<MyClass>(); // Moq1000: Sealed classes cannot be mocked
```

## Solution

```csharp
class MyClass { }

var mock = new Mock<MyClass>();
```
35 changes: 35 additions & 0 deletions docs/rules/Moq1001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Moq1001: Mocked interfaces cannot have constructor parameters

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
---

Mocking interfaces requires generating a class on-the-fly that implements the interface. That generated class is
constructed using the default constructor. To fix:

- Remove the constructor parameters

## Examples of patterns that are flagged by this analyzer

```csharp
interface IMyService
{
void Do(string s);
}

var mock = new Mock<IMyService>("123"); // Moq1001: Mocked interfaces cannot have constructor parameters
```

## Solution

```csharp
interface IMyService
{
void Do(string s);
}

var mock = new Mock<IMyService>();
```
34 changes: 34 additions & 0 deletions docs/rules/Moq1002.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Moq1002: Parameters provided into mock do not match any existing constructors

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
---

In order to construct the mocked type, constructor parameters must match a constructor. To fix:

- Match the arguments to `Mock` with a constructor of the mocked type

## Examples of patterns that are flagged by this analyzer

```csharp
class MyClass
{
MyClass(string s) { }
}

var mock = new Mock<MyClass>(3); // Moq1002: Parameters provided into mock do not match any existing constructors
```

## Solution

```csharp
class MyClass
{
MyClass(string s) { }
}

var mock = new Mock<MyClass>("three");
```
39 changes: 39 additions & 0 deletions docs/rules/Moq1100.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Moq1100: Callback signature must match the signature of the mocked method

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Warning |
| CodeFix | True |
---

The signature of the `.Callback()` method must match the signature of the `.Setup()` method. To fix:

- Ensure the parameters to `.Callback()` match the signature created by `.Setup()`. A code fix is available to automatically
match

## Examples of patterns that are flagged by this analyzer

```csharp
interface IMyService
{
int Do(int i, string s, DateTime dt);
}

var mock = new Mock<IMyService>()
.Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>()))
.Callback((string s1, int i1) => { }); // Moq1100: Callback signature must match the signature of the mocked method
```

## Solution

```csharp
interface IMyService
{
int Do(int i, string s, DateTime dt);
}

var mock = new Mock<IMyService>()
.Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>()))
.Callback((int i, string s, DateTime dt) => { });
```
32 changes: 32 additions & 0 deletions docs/rules/Moq1101.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Moq1101: SetupGet/SetupSet should be used for properties, not for methods

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
---

`.SetupGet()` and `.SetupSet()` are methods for mocking properties, not methods. Use `.Setup()` to mock methods instead.

## Examples of patterns that are flagged by this analyzer

```csharp
interface IMyInterface
{
string Method();
}

var mock = new Mock<IMyInterface>().SetupGet(x => x.Method()); // Moq1101: SetupGet/SetupSet should be used for properties, not for methods
```

## Solution

```csharp
interface IMyInterface
{
string Method();
}

var mock = new Mock<IMyInterface>().Setup(x => x.Method());
```
36 changes: 36 additions & 0 deletions docs/rules/Moq1200.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Moq1200: Setup should be used only for overridable members

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
---

Mocking requires generating a subclass of the class to be mocked. Methods not marked `virtual` cannot be overridden.
To fix:

- Mock an interface instead of a clas
- Make the method to be mocked `virtual`

```csharp
class SampleClass
{
int Property { get; set; }
}

var mock = new Mock<SampleClass>()
.Setup(x => x.Property); // Moq1200: Setup should be used only for overridable members
```

## Solution

```csharp
class SampleClass
{
virtual int Property { get; set; }
}

var mock = new Mock<SampleClass>()
.Setup(x => x.Property);
```
35 changes: 35 additions & 0 deletions docs/rules/Moq1201.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Moq1201: Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
---

Moq now supports the `.ReturnsAsync()` method to support mocking async methods. Use it instead of returning `.Result`,
[which can cause issues](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait).

## Examples of patterns that are flagged by this analyzer

```csharp
class AsyncClient
{
virtual Task<string> GetAsync() => Task.FromResult(string.Empty);
}

var mock = new Mock<AsyncClient>()
.Setup(c => c.GetAsync().Result); // Moq1201: Setup of async methods should use .ReturnsAsync instance instead of .Result
```

## Solution

```csharp
class AsyncClient
{
virtual Task<string> GetAsync() => Task.FromResult(string.Empty);
}

var mock = new Mock<AsyncClient>()
.Setup(c => c.GetAsync()).ReturnsAsync(string.Empty);
```
47 changes: 47 additions & 0 deletions docs/rules/Moq1300.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Moq1300: `Mock.As()` should take interfaces only

| Item | Value |
| --- | --- |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
---

The `.As()` method is used when a mocked object must implement multiple interfaces. It cannot be used with abstract or
concrete classes. To fix:

- Change the method to use an interface
- Remove the `.As()` method

## Examples of patterns that are flagged by this analyzer

```csharp
interface ISampleInterface
{
int Calculate(int a, int b);
}

class SampleClass
{
int Calculate() => 0;
}

var mock = new Mock<SampleClass>()
.As<SampleClass>(); // Moq1300: Mock.As() should take interfaces only
```

## Solution

```csharp
interface ISampleInterface
{
int Calculate(int a, int b);
}

class SampleClass
{
int Calculate() => 0;
}

var mock = new Mock<ISampleInterface>();
```
12 changes: 12 additions & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Diagnostics / rules

| ID | Title |
| --- | --- |
[Moq1000](./Moq1000.md) | Sealed classes cannot be mocked
[Moq1001](./Moq1001.md) | Mocked interfaces cannot have constructor parameters
[Moq1002](./Moq1002.md) | Parameters provided into mock do not match any existing constructors
[Moq1100](./Moq1100.md) | Callback signature must match the signature of the mocked method
[Moq1101](./Moq1101.md) | SetupGet/SetupSet should be used for properties, not for methods
[Moq1200](./Moq1200.md) | Setup should be used only for overridable members
[Moq1201](./Moq1201.md) | Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`
[Moq1300](./Moq1300.md) | `Mock.As()` should take interfaces only
Loading