Skip to content

Commit

Permalink
Clean up README and add docs for each analyzer rule (#77)
Browse files Browse the repository at this point in the history
Made a pass at the README and other docs to make the project easier to
use.

- Moved the code of conduct out of README into `CODE-OF-CONDUCT.md` so
that GitHub can pick it up automatically and add it to the UI
- Move the contributing docs out of README and into `CONTRIBUTING.md` so
that GitHub can pick it up automatically and add it to the UI
- Add NuGet and build badges to README, for #15
- Small copy edits to README
- Added a `//docs/rules/` folder and added a doc per rule with an
explanation and sample
  • Loading branch information
MattKotsenas authored Jun 10, 2024
1 parent 9a3e801 commit 5a8cad8
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 18 deletions.
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

0 comments on commit 5a8cad8

Please sign in to comment.