From b030c040fa5e121603d11d14326a4ab2dcf536f5 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:00:52 -0700 Subject: [PATCH 1/7] Hard wrap README --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c96a9424..369e14844 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # 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: +**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: ## Detected issues @@ -21,10 +23,16 @@ You must use an in-support version of the .NET SDK (i.e. 6+). ## Contributions are welcome! -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. +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. -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). +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). ## Code of Conduct -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) +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) From dd050164e5f11222f6e4713faccab11d6f71a191 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:05:37 -0700 Subject: [PATCH 2/7] Move code of conduct to CODE-OF-CONDUCT.md so GitHub can pick it up --- CODE-OF-CONDUCT.md | 6 ++++++ README.md | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 CODE-OF-CONDUCT.md diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..775f221c9 --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -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). diff --git a/README.md b/README.md index 369e14844..dcbcf5ab7 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,3 @@ 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). - -## Code of Conduct - -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) From 825c59b50061d3bfd54c6e5537d09e49b0a9f178 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:10:36 -0700 Subject: [PATCH 3/7] Create CONTRIBUTING.md to GitHub can pick it up --- CONTRIBUTING.md | 7 +++++++ README.md | 9 ++------- 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..562e9329d --- /dev/null +++ b/CONTRIBUTING.md @@ -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). diff --git a/README.md b/README.md index dcbcf5ab7..8b664e480 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,7 @@ Install ["Moq.Analyzers" NuGet package](https://www.nuget.org/packages/Moq.Analy You must use an in-support version of the .NET SDK (i.e. 6+). -## Contributions are welcome! +## Contributions welcome 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. - -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). +develop new features, improve the documentation, or do other cool stuff. See [CONTRIBUTING.md](./CONTRIBUTING.md). From b677434b5146a044f55335de69537f0e0ebd5113 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:20:28 -0700 Subject: [PATCH 4/7] Copy edit installation section --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8b664e480..539712571 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,17 @@ something is wrong with your Moq configuration: * Moq1201 = Setup of async methods should use `.ReturnsAsync` instance instead of `.Result`. * Moq1300 = Mock.As() should take interfaces. -## How to install -Install ["Moq.Analyzers" NuGet package](https://www.nuget.org/packages/Moq.Analyzers) into test projects using Moq. +## Getting started -You must use an in-support version of the .NET SDK (i.e. 6+). +Moq.Analyzers is installed from NuGet. Run this command for your test project(s): + +```powershell +dotnet add package Moq.Analyzers +``` + +> 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). ## Contributions welcome From d6a79d0b78faba71f9d0bc1746bb6fad017c06b8 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:20:49 -0700 Subject: [PATCH 5/7] Copy edit intro --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 539712571..9842f3efd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # 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: +**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. ## Detected issues From 1949ec51207d8079d6f240e0d16ebe50361fd4b6 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 12:28:19 -0700 Subject: [PATCH 6/7] Add badges to intro --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9842f3efd..d7b7938fe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Moq.Analyzers +[![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) + **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. From aa116fb2bdf9b11131e3706c52dc01ef924ba9d0 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Mon, 10 Jun 2024 13:58:36 -0700 Subject: [PATCH 7/7] Add docs for each analyzer rule --- README.md | 23 +++++++++++---------- docs/rules/Moq1000.md | 30 +++++++++++++++++++++++++++ docs/rules/Moq1001.md | 35 ++++++++++++++++++++++++++++++++ docs/rules/Moq1002.md | 34 +++++++++++++++++++++++++++++++ docs/rules/Moq1100.md | 39 +++++++++++++++++++++++++++++++++++ docs/rules/Moq1101.md | 32 +++++++++++++++++++++++++++++ docs/rules/Moq1200.md | 36 +++++++++++++++++++++++++++++++++ docs/rules/Moq1201.md | 35 ++++++++++++++++++++++++++++++++ docs/rules/Moq1300.md | 47 +++++++++++++++++++++++++++++++++++++++++++ docs/rules/README.md | 12 +++++++++++ 10 files changed, 312 insertions(+), 11 deletions(-) create mode 100644 docs/rules/Moq1000.md create mode 100644 docs/rules/Moq1001.md create mode 100644 docs/rules/Moq1002.md create mode 100644 docs/rules/Moq1100.md create mode 100644 docs/rules/Moq1101.md create mode 100644 docs/rules/Moq1200.md create mode 100644 docs/rules/Moq1201.md create mode 100644 docs/rules/Moq1300.md create mode 100644 docs/rules/README.md diff --git a/README.md b/README.md index d7b7938fe..366e898bc 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,18 @@ [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. -## Detected issues - -* 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 + +* 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 + +See [docs/rules](./docs/rules/README.md) for full documentation. ## Getting started diff --git a/docs/rules/Moq1000.md b/docs/rules/Moq1000.md new file mode 100644 index 000000000..b7857ed0d --- /dev/null +++ b/docs/rules/Moq1000.md @@ -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(); // Moq1000: Sealed classes cannot be mocked +``` + +## Solution + +```csharp +class MyClass { } + +var mock = new Mock(); +``` diff --git a/docs/rules/Moq1001.md b/docs/rules/Moq1001.md new file mode 100644 index 000000000..e9f7b6f6d --- /dev/null +++ b/docs/rules/Moq1001.md @@ -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("123"); // Moq1001: Mocked interfaces cannot have constructor parameters +``` + +## Solution + +```csharp +interface IMyService +{ + void Do(string s); +} + +var mock = new Mock(); +``` diff --git a/docs/rules/Moq1002.md b/docs/rules/Moq1002.md new file mode 100644 index 000000000..42359aceb --- /dev/null +++ b/docs/rules/Moq1002.md @@ -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(3); // Moq1002: Parameters provided into mock do not match any existing constructors +``` + +## Solution + +```csharp +class MyClass +{ + MyClass(string s) { } +} + +var mock = new Mock("three"); +``` diff --git a/docs/rules/Moq1100.md b/docs/rules/Moq1100.md new file mode 100644 index 000000000..26ed98bf4 --- /dev/null +++ b/docs/rules/Moq1100.md @@ -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() + .Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())) + .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() + .Setup(x => x.Do(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((int i, string s, DateTime dt) => { }); +``` diff --git a/docs/rules/Moq1101.md b/docs/rules/Moq1101.md new file mode 100644 index 000000000..1760b89d1 --- /dev/null +++ b/docs/rules/Moq1101.md @@ -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().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().Setup(x => x.Method()); +``` diff --git a/docs/rules/Moq1200.md b/docs/rules/Moq1200.md new file mode 100644 index 000000000..60d83132d --- /dev/null +++ b/docs/rules/Moq1200.md @@ -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() + .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() + .Setup(x => x.Property); +``` diff --git a/docs/rules/Moq1201.md b/docs/rules/Moq1201.md new file mode 100644 index 000000000..1f4e6250e --- /dev/null +++ b/docs/rules/Moq1201.md @@ -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 GetAsync() => Task.FromResult(string.Empty); +} + +var mock = new Mock() + .Setup(c => c.GetAsync().Result); // Moq1201: Setup of async methods should use .ReturnsAsync instance instead of .Result +``` + +## Solution + +```csharp +class AsyncClient +{ + virtual Task GetAsync() => Task.FromResult(string.Empty); +} + +var mock = new Mock() + .Setup(c => c.GetAsync()).ReturnsAsync(string.Empty); +``` diff --git a/docs/rules/Moq1300.md b/docs/rules/Moq1300.md new file mode 100644 index 000000000..42de793b0 --- /dev/null +++ b/docs/rules/Moq1300.md @@ -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() + .As(); // 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(); +``` diff --git a/docs/rules/README.md b/docs/rules/README.md new file mode 100644 index 000000000..e6be38654 --- /dev/null +++ b/docs/rules/README.md @@ -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