# Strengthening Moq based unit tests with PosInformatique.Moq.Analyzers

As a passionate advocate for robust unit testing and code quality, I've invested heavily in leveraging the [Moq](https://github.com/devlooped/moq) library within my .NET projects. To ensure reliability and catch potential errors early, I developed a .NET analyzer tool specifically tailored for Moq based unit tests. This analyzer is called [PosInformatique.Moq.Analyzers](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers) and available on [nuget.org](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers) web site.

### **Origin and Purpose**

This analyzer was born from the need to fortify unit tests by detecting discrepancies during the coding phase. I wanted to preemptively identify errors in my [Moq](https://github.com/devlooped/moq) based tests to ensure their coherence and correctness.

For that I used the .NET Compiler Platform SDK to create an analyzer packaged inside a NuGet package.

### **What's new in the version 1.3.0?**

The recent release, version [1.3.0](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers/1.3.0), brings a significant advancement: the [PosInfoMoq2003](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2003.html) rule. This rule inspects method signature within `Callback()` to validate their alignment with `Setup()` method.

For exemple, in the following mocked method `UpdateAsync()`, the analyzer check that the `Callback()` method take a lambda expression with exactly the same signature.

```csharp
var repository = new Mock<ITaskRepository>(MockBehavior.Strict);
repository.Setup(r => r.UpdateAsync(task, It.IsAny<TaskChangeTracking>()))
    .Callback((TaskUpdate _, TaskChangeTracking updated) =>
    {
        updated.DateTime.Should().Be(new DateTime(2023, 1, 2, 3, 4, 5, 6)).And.BeIn(DateTimeKind.Utc);
        updated.UserId.Should().Be("55555555-5555-5555-5555-555555555555");
    })
    .Returns(Task.CompletedTask);
```

This enhancement significantly boosts the analyzer's ability to ensure method coherence within Moq setups.

### **A quick recap of the previous versions**

Let's recall the enhancements of the previous versions. They introduced rules like [PosInfoMoq1000](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Design/PosInfoMoq1000.html) and [PosInfoMoq1001](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Design/PosInfoMoq1001.html), emphasizing the importance of invoking `Verify()` or `VerifyAll()` methods and defining `Mock<T>` instances to `Strict` mode.

Also, other additional rules was added, to perform immediate **compilation validation**, aiming to catch errors before runtime execution, ensuring higher code quality and mitigating potential issues during the testing phase.

* [PosInfoMoq2000](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2000.html): Requires the use of `Returns()` or `ReturnsAsync()` methods for `Strict` mocks. This rule ensures that when defining a `Mock<T>` with `Strict` behavior, these methods are invoked when setting up a method to mock returning a value.
    
* [PosInfoMoq2001](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2001.html): Check the usage of the `Setup()` method to be use on overridable members (not extension method, not sealed).
    
* [PosInfoMoq2002](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2002.html)**:** Specifies that the `Mock<T>` class can only be used to mock non-sealed classes (interfaces of abstract classes).
    

For instance, this is a simple unit test which matches four of the previous rules of the [PosInformatique.Moq.Analyzers](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers) analyzer.

```csharp
[Fact]
public async Task GetAsync()
{
    var visits = new[]
    {
        new Visit(default, default, default),
    };

    var manager = new Mock<IVisitManager>(MockBehavior.Strict);  // PosInfoMoq1001 : Check that mock are defined to Strict mode.
    manager.Setup(m => m.FindAsync(It.IsAny<VisitQuery>()))
        .Callback((VisitQuery q) =>  // PosInfoMoq2003: Check the signature to match with the Setup() mocked method.
        {
            q.Should().BeEquivalentTo(new VisitQuery()
            {
                Id = new Guid("11111111-1111-1111-1111-111111111111"),
            });
        })
        .ReturnsAsync(visits);  // PosInfoMoq2000: Returns() or ReturnsAsync() is required.

    var result = await manager.Object.GetAsync(new Guid("11111111-1111-1111-1111-111111111111"));

    result.Should().BeSameAs(visits[0]);

    manager.VerifyAll();  // PosInfoMoq1000 : Check that the Verify() / VerifyAll() method is called in the unit test.
}
```

For detailed installation instructions and a comprehensive list of rules, refer to the official documentation available on the [github.io website](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/).

### **Useful Links**

* **Official Documentation:** Access the detailed documentation for [PosInformatique.Moq.Analyzers](https://posinformatique.github.io/PosInformatique.Moq.Analyzers/).
    
* **GitHub Repository:** Explore and contribute to the analyzer's development on [PosInformatique.Moq.Analyzers GitHub repository](https://github.com/PosInformatique/PosInformatique.Moq.Analyzers).
