Skip to content

Commit

Permalink
Add Specs for illegal attempted use of fluent API
Browse files Browse the repository at this point in the history
  • Loading branch information
blairconrad committed Feb 22, 2021
1 parent 8ec6cf9 commit 3d29874
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 1 deletion.
2 changes: 1 addition & 1 deletion tests/FakeItEasy.Specs/FakeItEasy.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
<PackageReference Include="Xbehave.Core" Version="2.4.1" />
<ProjectReference Include="..\..\src\FakeItEasy\FakeItEasy.csproj" />
<ProjectReference Include="..\..\src\FakeItEasy.Extensions.ValueTask\FakeItEasy.Extensions.ValueTask.csproj" />
Expand Down
183 changes: 183 additions & 0 deletions tests/FakeItEasy.Specs/IllegalFluentApiSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
namespace FakeItEasy.Specs
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using FakeItEasy.Tests.TestHelpers;
using FluentAssertions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xbehave;

/// <summary>
/// Check that certain fluent API syntax constructions are illegal.
/// </summary>
public static class IllegalFluentApiSpecs
{
private const string FakeUsageTemplate = @"
namespace ApiTest
{{
using FakeItEasy;
public class Test
{{
public interface IService
{{
void AVoidMethod();
int AnIntMethod();
int AProperty {{ get; set; }}
}}
public void TestMethod()
{{
var fake = A.Fake<IService>();
{0};
}}
}}
}}
";

private static IEnumerable<Microsoft.CodeAnalysis.MetadataReference> references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.Where(CompilationRequiresAssembly)
.Select(a => MetadataReference.CreateFromFile(a.Location))
.ToList();

[Scenario]
[UsingCulture("en-US")]
[Example(
"A.CallTo(fake).Returns(3)",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallTo(fake).WithReturnType<string>().Returns(3)",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallTo(fake).Invokes(() => {}).MustHaveHappened(4, Times.OrLess)",
"No overload for method 'MustHaveHappened'")]
[Example(
"A.CallTo(fake).Invokes(() => {}).MustHaveHappenedANumberOfTimesMatching(count => true)",
"does not contain a definition for 'MustHaveHappenedANumberOfTimesMatching'")]
[Example(
"A.CallTo(fake).CallsWrappedMethod().MustHaveHappened(4, Times.OrLess)",
"No overload for method 'MustHaveHappened'")]
[Example(
"A.CallTo(fake).CallsWrappedMethod().MustHaveHappenedANumberOfTimesMatching(count => true)",
"does not contain a definition for 'MustHaveHappenedANumberOfTimesMatching'")]
[Example(
"A.CallTo(fake).WithNonVoidReturnType().Returns(0).MustHaveHappened(4, Times.OrLess)",
"No overload for method 'MustHaveHappened'")]
[Example(
"A.CallTo(fake).WithNonVoidReturnType().Returns(0).MustHaveHappenedANumberOfTimesMatching(count => true)",
"does not contain a definition for 'MustHaveHappenedANumberOfTimesMatching'")]
[Example(
"A.CallTo(fake).WhenArgumentsMatch(call => true).WithNonVoidReturnType()",
"does not contain a definition for 'WithNonVoidReturnType'")]
[Example(
"A.CallTo(() => fake.AVoidMethod()).WithNonVoidReturnType()",
"does not contain a definition for 'WithNonVoidReturnType'")]
[Example(
"A.CallTo(() => fake.AVoidMethod()).WithReturnType<string>()",
"does not contain a definition for 'WithReturnType'")]
[Example(
"A.CallTo(() => fake.AVoidMethod()).Returns(3)",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallTo(() => fake.AnIntMethod()).WithNonVoidReturnType()",
"does not contain a definition for 'WithNonVoidReturnType'")]
[Example(
"A.CallTo(() => fake.AnIntMethod()).WithReturnType<int>()",
"does not contain a definition for 'WithReturnType'")]
[Example(
"A.CallTo(() => fake.AnIntMethod()).Returns(new object())",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallTo(() => fake.AnIntMethod()).Returns(3).Invokes(() => {})",
"does not contain a definition for 'Invokes'")]
[Example(
"A.CallTo(() => fake.AnIntMethod()).Throws<System.Exception>().MustHaveHappened()",
"does not contain a definition for 'MustHaveHappened'")]
[Example(
"A.CallToSet(() => fake.AProperty).Returns(3)",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallToSet(() => fake.AProperty).SetsOutAndRefParameters('a')",
"does not contain a definition for 'SetsOutAndRefParameters'")]
[Example(
"A.CallToSet(() => fake.AProperty).WithReturnType<string>()",
"does not contain a definition for 'WithReturnType'")]
[Example(
"A.CallToSet(() => fake.AProperty).WithNonVoidReturnType()",
"does not contain a definition for 'WithNonVoidReturnType'")]
[Example(
"A.CallToSet(() => fake.AProperty).Invokes(() => {}).MustHaveHappened()",
"does not contain a definition for 'MustHaveHappened'")]
[Example(
"A.CallToSet(() => fake.AProperty).Throws<System.Exception>().MustHaveHappened()",
"does not contain a definition for 'MustHaveHappened'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).Returns(3)",
"does not contain a definition for 'Returns'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).SetsOutAndRefParameters('a')",
"does not contain a definition for 'SetsOutAndRefParameters'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).WithReturnType<string>()",
"does not contain a definition for 'WithReturnType'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).WithNonVoidReturnType()",
"does not contain a definition for 'WithNonVoidReturnType'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).Invokes(() => {}).MustHaveHappened()",
"does not contain a definition for 'MustHaveHappened'")]
[Example(
"A.CallToSet(() => fake.AProperty).To(7).Throws<System.Exception>().MustHaveHappened()",
"does not contain a definition for 'MustHaveHappened'")]
public static void IllegalSyntax(string illegalOperation, string expectedError, string illegalUsage, IEnumerable<Diagnostic> diagnostics)
{
"Given an illegal usage of a fake"
.x(() => illegalUsage = string.Format(CultureInfo.InvariantCulture, FakeUsageTemplate, illegalOperation));

"When I compile the usage"
.x(() => diagnostics = Compile(illegalUsage));

"Then the compilation fails"
.x(() => diagnostics.Should().ContainSingle().Which.GetMessage().Should()
.Contain(expectedError));
}

private static bool CompilationRequiresAssembly(Assembly a)
{
var assemblyName = a.GetName().Name;
return
assemblyName == "mscorlib" ||
assemblyName == "netstandard" ||
assemblyName == "System.Core" ||
assemblyName == "System.Linq.Expressions" ||
assemblyName == "System.Private.CoreLib" ||
assemblyName == "System.Runtime" ||
assemblyName == "FakeItEasy";
}

private static System.Collections.Immutable.ImmutableArray<Diagnostic> Compile(string source)
{
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOverflowChecks(true)
.WithOptimizationLevel(OptimizationLevel.Release);

var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(
source,
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest),
string.Empty);

var compilation = CSharpCompilation.Create(
"Test.dll",
new[] { parsedSyntaxTree },
references,
compilationOptions);
return compilation.GetDiagnostics();
}
}
}

0 comments on commit 3d29874

Please sign in to comment.