Skip to content

Commit

Permalink
Refactor unit tests: data driven tests and leverage Microsoft.CodeAna…
Browse files Browse the repository at this point in the history
…lysis.Testing patterns (#76)

After the switch to using `Microsoft.CodeAnalysis.Testing`, refactor the
tests to better follow the test infrastructure conventions. (For
additional examples, check out the dotnet/roslyn-analyzers repo).

1. Use helper classes instead of inheritance to make the tests more
flexible
2. Centralize test defaults in a new `Test` class rather than in a base
class
3. Add a set of global usings to the default test to simplify tests and
make test authoring easier
4. Use xUnit's data-driven test method to simplify test scenarios
- Allows for a matrix of tests, both in the global namespace (fixes #56)
and in a given namespace
- Makes test debugging a bit easier as there's only a single Moq call
asserted in each test
- Makes reasoning about the test a bit more difficult, as now there's
distance between the test clause and the setup
5. After the test refactoring, there were a few cases of clear
duplication; removed and verified code coverage numbers are the same

I tried to also create "doppelganger" tests in this PR, like in
`NoConstructorArgumentsForInterfaceMockAnalyzerTests` where a user
creates a `Mock<T>` class in the namespace to make sure the analyzers
are doing fully-qualified name resolution. However, to do that
generically requires duplicating a lot of more of Moq's own API, and I'm
not sure if that's a good idea, so filed #75 to track.

There's probably more that can be done to DRY out the test templates,
but I wanted to make sure this was a good step forward before going any
further.
  • Loading branch information
MattKotsenas authored Jun 10, 2024
1 parent f52ba4b commit 9a3e801
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 1,153 deletions.
220 changes: 0 additions & 220 deletions Source/Moq.Analyzers.Test/AbstractClassTests.cs

This file was deleted.

118 changes: 22 additions & 96 deletions Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,29 @@
using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier<Moq.Analyzers.AsShouldBeUsedOnlyForInterfaceAnalyzer>;

namespace Moq.Analyzers.Test;

public class AsAcceptOnlyInterfaceAnalyzerTests : DiagnosticVerifier<AsShouldBeUsedOnlyForInterfaceAnalyzer>
public class AsAcceptOnlyInterfaceAnalyzerTests
{
[Fact]
public async Task ShouldFailWhenUsingAsWithAbstractClass()
public static IEnumerable<object[]> TestData()
{
await VerifyCSharpDiagnostic(
"""
using Moq;
namespace AsAcceptOnlyInterface.TestBadAsForAbstractClass;
public abstract class BaseSampleClass
{
public int Calculate() => 0;
}
internal class MyUnitTests
{
private void TestBadAsForAbstractClass()
{
var mock = new Mock<BaseSampleClass>();
mock.As<{|Moq1300:BaseSampleClass|}>();
}
}
""");
foreach (var @namespace in new[] { string.Empty, "namespace MyNamespace;" })
{
// TODO: .As<BaseSampleClass> and .As<SampleClass> feels redundant
yield return [@namespace, """new Mock<BaseSampleClass>().As<{|Moq1300:BaseSampleClass|}>();"""];
yield return [@namespace, """new Mock<BaseSampleClass>().As<{|Moq1300:SampleClass|}>();"""];
yield return [@namespace, """new Mock<SampleClass>().As<ISampleInterface>();"""];
// TODO: Testing with .Setup() and .Returns() seems unnecessary.
yield return [@namespace, """new Mock<SampleClass>().As<ISampleInterface>().Setup(x => x.Calculate(It.IsAny<int>(), It.IsAny<int>())).Returns(10);"""];
}
}

[Fact]
public async Task ShouldFailWhenUsingAsWithConcreteClass()
[Theory]
[MemberData(nameof(TestData))]
public async Task ShouldAnalyzeAs(string @namespace, string mock)
{
await VerifyCSharpDiagnostic(
"""
using Moq;
namespace AsAcceptOnlyInterface.TestBadAsForNonAbstractClass;
await Verifier.VerifyAnalyzerAsync(
$$"""
{{@namespace}}
public interface ISampleInterface
{
Expand All @@ -46,80 +35,17 @@ public abstract class BaseSampleClass
public int Calculate() => 0;
}
public class OtherClass
{
public int Calculate() => 0;
}
internal class MyUnitTests
{
private void TestBadAsForNonAbstractClass()
{
var mock = new Mock<BaseSampleClass>();
mock.As<{|Moq1300:OtherClass|}>();
}
}
""");
}

[Fact]
public async Task ShouldPassWhenUsingAsWithInterface()
{
await VerifyCSharpDiagnostic(
"""
using Moq;
namespace AsAcceptOnlyInterface.TestOkAsForInterface;
public interface ISampleInterface
{
int Calculate(int a, int b);
}
public class SampleClass
{
public int Calculate() => 0;
}
internal class MyUnitTests
{
private void TestOkAsForInterface()
{
var mock = new Mock<SampleClass>();
mock.As<ISampleInterface>();
}
}
""");
}

[Fact]
public async Task ShouldPassWhenUsingAsWithInterfaceWithSetup()
{
await VerifyCSharpDiagnostic(
"""
using Moq;
namespace AsAcceptOnlyInterface.TestOkAsForInterfaceWithConfiguration;
public interface ISampleInterface
{
int Calculate(int a, int b);
}
public class SampleClass
{
public int Calculate() => 0;
}
internal class MyUnitTests
internal class UnitTest
{
private void TestOkAsForInterfaceWithConfiguration()
private void Test()
{
var mock = new Mock<SampleClass>();
mock.As<ISampleInterface>()
.Setup(x => x.Calculate(It.IsAny<int>(), It.IsAny<int>()))
.Returns(10);
{{mock}}
}
}
""");
Expand Down
Loading

0 comments on commit 9a3e801

Please sign in to comment.