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
37 changes: 37 additions & 0 deletions docs/rules/Moq1100.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,43 @@ var mock = new Mock<IMyService>()
.Callback(new DoCallback((int i, string s, DateTime dt) => { }));
```

## Advanced Patterns Supported

This analyzer supports comprehensive validation of advanced callback patterns:

### Multiple Callback Timing

```csharp
mock.Setup(x => x.DoWork("test"))
.Callback(() => Console.WriteLine("Before"))
.Returns(42)
.Callback(() => Console.WriteLine("After"));
```

### Ref/Out Parameter Callbacks

```csharp
delegate void ProcessDataCallback(ref string data);
mock.Setup(x => x.ProcessData(ref It.Ref<string>.IsAny))
.Callback(new ProcessDataCallback((ref string data) => data = "processed"));
```

### Complex Multi-Parameter Scenarios

```csharp
mock.Setup(x => x.ProcessMultiple(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>()))
.Callback((int id, string name, DateTime timestamp) => Console.WriteLine($"Processing {id}"));
Comment thread
rjmurillo marked this conversation as resolved.
```

### Out Parameter Delegates

```csharp
delegate bool TryProcessCallback(out int result);
mock.Setup(x => x.TryProcess(out It.Ref<int>.IsAny))
.Callback(new TryProcessCallback((out int result) => { result = 42; }))
.Returns(true);
```

## Suppress a warning

If you just want to suppress a single violation, add preprocessor directives to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using Moq.Analyzers.Test.Helpers;

Check warning on line 1 in tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodAnalyzerTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Test/CallbackSignatureShouldMatchMockedMethodAnalyzerTests.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

using AnalyzerVerifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier<Moq.Analyzers.CallbackSignatureShouldMatchMockedMethodAnalyzer>;

namespace Moq.Analyzers.Test;

/// <summary>
/// Comprehensive tests for the CallbackSignatureShouldMatchMockedMethodAnalyzer.
/// Validates all advanced callback patterns including ref/out parameters, multiple callbacks,
/// generic callbacks, and complex scenarios from issue #434.
/// </summary>
public class CallbackSignatureShouldMatchMockedMethodAnalyzerTests(ITestOutputHelper output)
{
/// <summary>
/// Consolidated test data for all callback validation scenarios.
/// Combines valid patterns (should not trigger diagnostics) and invalid patterns (should trigger diagnostics).
/// </summary>
/// <returns>Test data for comprehensive callback validation scenarios.</returns>
public static IEnumerable<object[]> CallbackValidationData()
{
// Valid patterns that should NOT trigger the analyzer
IEnumerable<object[]> validPatterns = new object[][]
{
// Multiple callbacks with correct signatures
["""new Mock<IFoo>().Setup(x => x.DoWork("test")).Callback(() => { }).Returns(42).Callback(() => { });"""],

// Ref parameter with correct signature
["""new Mock<IFoo>().Setup(m => m.DoRef(ref It.Ref<string>.IsAny)).Callback((ref string data) => { });"""],

// Out parameter with correct signature
["""new Mock<IFoo>().Setup(m => m.DoOut(out It.Ref<int>.IsAny)).Callback((out int result) => { result = 42; });"""],

// Basic callback with correct parameter type
["""new Mock<IFoo>().Setup(x => x.DoWork("test")).Callback((string param) => { });"""],

// No parameters callback for parameterized method (valid pattern)
["""new Mock<IFoo>().Setup(x => x.DoWork("test")).Callback(() => { });"""],

// Complex multiple parameter with correct signatures
["""new Mock<IFoo>().Setup(x => x.ProcessMultiple(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback((int id, string name, DateTime timestamp) => { });"""],
Comment thread
rjmurillo marked this conversation as resolved.
}.WithNamespaces().WithMoqReferenceAssemblyGroups();

// Invalid patterns that SHOULD trigger the analyzer
IEnumerable<object[]> invalidPatterns = new object[][]
{
// Basic callback with wrong parameter type
["""new Mock<IFoo>().Setup(x => x.DoWork("test")).Callback(({|Moq1100:int wrongParam|}) => { });"""],

// Ref parameter mismatch (missing ref)
["""new Mock<IFoo>().Setup(m => m.DoRef(ref It.Ref<string>.IsAny)).Callback(({|Moq1100:string data|}) => { });"""],

// Out parameter mismatch (missing out)
["""new Mock<IFoo>().Setup(m => m.DoOut(out It.Ref<int>.IsAny)).Callback(({|Moq1100:int result|}) => { });"""],
}.WithNamespaces().WithMoqReferenceAssemblyGroups();

return validPatterns.Concat(invalidPatterns);
}

[Theory]
[MemberData(nameof(CallbackValidationData))]
public async Task ShouldValidateCallbackPatterns(string referenceAssemblyGroup, string @namespace, string testCode)
{
static string Template(string ns, string code) =>
$$"""
{{ns}}

public interface IFoo
{
int DoWork(string input);
bool ProcessMultiple(int id, string name, DateTime timestamp);
void ProcessData(ref string data);
bool TryProcess(out int result);
void ProcessMixed(int id, ref string data, out bool success);
void ProcessReadOnly(in DateTime timestamp);
int DoRef(ref string data);
bool DoOut(out int result);
string DoIn(in DateTime timestamp);
T ProcessGeneric<T>(T input);
}

public class TestClass
{
public void TestMethod()
{
{{code}}
}
}
""";

string source = Template(@namespace, testCode);
output.WriteLine(source);
await AnalyzerVerifier.VerifyAnalyzerAsync(source, referenceAssemblyGroup);
Comment thread
rjmurillo marked this conversation as resolved.
}

/// <summary>
/// Test to document the current limitation with generic callback validation.
/// This test documents that .Callback&lt;T&gt;() with wrong type parameters is NOT currently validated.
/// This could be enhanced in a future version.
/// </summary>
/// <returns>A task representing the asynchronous unit test.</returns>
[Fact]
public async Task GenericCallbackValidation_CurrentLimitation_IsDocumented()
{
const string source = """
using Moq;

public interface IFoo
{
int DoWork(string input);
}

public class TestClass
{
public void TestGenericCallback()
{
var mock = new Mock<IFoo>();
// Note: This currently does NOT trigger a diagnostic, which could be enhanced in the future
Comment thread
rjmurillo marked this conversation as resolved.
mock.Setup(x => x.DoWork("test"))
.Callback<int>(wrongTypeParam => { }); // Should ideally trigger Moq1100 but currently doesn't
}
}
""";

// This test documents the current limitation - no diagnostic is expected
await AnalyzerVerifier.VerifyAnalyzerAsync(source, "Net80WithOldMoq");
}
}
Loading