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
55 changes: 55 additions & 0 deletions TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,61 @@ void M()
return VerifyGeneratorOutput(source);
}

[Test]
public Task Interface_With_Nullable_Event_Args()
{
// Regression test for https://github.com/thomhurst/TUnit/issues/5425
// Nullability of generic event handler type arguments must be preserved
// in the generated mock to avoid CS8604.
var source = """
#nullable enable
using System;
using TUnit.Mocks;

public interface IFoo
{
event EventHandler<string?> Something;
}

public class TestUsage
{
void M()
{
var mock = Mock.Of<IFoo>();
}
}
""";

return VerifyGeneratorOutput(source);
}

[Test]
public Task Interface_With_Nullable_Event_And_Nullable_Args()
{
// Combined regression for #5424 + #5425: both the delegate itself and
// its generic type argument are nullable (`EventHandler<string?>?`).
var source = """
#nullable enable
using System;
using TUnit.Mocks;

public interface IFoo
{
event EventHandler<string?>? Something;
}

public class TestUsage
{
void M()
{
var mock = Mock.Of<IFoo>();
}
}
""";

return VerifyGeneratorOutput(source);
}

[Test]
public Task Interface_With_Multiple_Multi_Parameter_Events()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
public sealed class IFooMock : global::TUnit.Mocks.Mock<global::IFoo>, global::IFoo
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal IFooMock(global::IFoo mockObject, global::TUnit.Mocks.MockEngine<global::IFoo> engine)
: base(mockObject, engine) { }

event global::System.EventHandler<string?>? global::IFoo.Something { add => Object.Something += value; remove => Object.Something -= value; }
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public readonly struct IFoo_MockEvents
{
internal readonly global::TUnit.Mocks.MockEngine<global::IFoo> Engine;

internal IFoo_MockEvents(global::TUnit.Mocks.MockEngine<global::IFoo> engine) => Engine = engine;
}

public static class IFoo_MockEventsExtensions
{
extension(global::TUnit.Mocks.Mock<global::IFoo> mock)
{
public IFoo_MockEvents Events => new(global::TUnit.Mocks.MockRegistry.GetEngine(mock));
}

extension(IFoo_MockEvents events)
{
public global::TUnit.Mocks.EventSubscriptionAccessor Something
=> new(events.Engine, "Something");
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
file sealed class IFooMockImpl : global::IFoo, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject
{
private readonly global::TUnit.Mocks.MockEngine<global::IFoo> _engine;

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; }

internal IFooMockImpl(global::TUnit.Mocks.MockEngine<global::IFoo> engine)
{
_engine = engine;
}

private global::System.EventHandler<string?>? _backing_Something;

public event global::System.EventHandler<string?>? Something
{
add { _backing_Something += value; _engine.RecordEventSubscription("Something", true); }
remove { _backing_Something -= value; _engine.RecordEventSubscription("Something", false); }
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal void Raise_Something(string? e)
{
_backing_Something?.Invoke(this, e);
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public void RaiseEvent(string eventName, object? args)
{
switch (eventName)
{
case "Something":
{
Raise_Something((string?)args!);
break;
}
default:
throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock.");
}
}
}

file static class IFooMockFactory
{
[global::System.Runtime.CompilerServices.ModuleInitializer]
internal static void Register()
{
global::TUnit.Mocks.MockRegistry.RegisterFactory<global::IFoo>(Create);
}

internal static global::TUnit.Mocks.Mock<global::IFoo> Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs)
{
if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IFoo' does not support constructor arguments, but {constructorArgs.Length} were provided.");
var engine = new global::TUnit.Mocks.MockEngine<global::IFoo>(behavior);
var impl = new IFooMockImpl(engine);
engine.Raisable = impl;
var mock = new IFooMock(impl, engine);
return mock;
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
public static class IFoo_MockMemberExtensions
{
public static void RaiseSomething(this global::TUnit.Mocks.Mock<global::IFoo> mock, string? e)
{
((global::TUnit.Mocks.IRaisable)global::TUnit.Mocks.MockRegistry.GetEngine(mock).Raisable!).RaiseEvent("Something", (object?)e);
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks
{
public static class IFoo_MockStaticExtension
{
extension(global::IFoo)
{
public static global::TUnit.Mocks.Generated.IFooMock Mock(global::TUnit.Mocks.MockBehavior behavior = global::TUnit.Mocks.MockBehavior.Loose)
{
return (global::TUnit.Mocks.Generated.IFooMock)global::TUnit.Mocks.Mock.Of<global::IFoo>(behavior);
}
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated;
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
public sealed class IFooMock : global::TUnit.Mocks.Mock<global::IFoo>, global::IFoo
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal IFooMock(global::IFoo mockObject, global::TUnit.Mocks.MockEngine<global::IFoo> engine)
: base(mockObject, engine) { }

event global::System.EventHandler<string?> global::IFoo.Something { add => Object.Something += value; remove => Object.Something -= value; }
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public readonly struct IFoo_MockEvents
{
internal readonly global::TUnit.Mocks.MockEngine<global::IFoo> Engine;

internal IFoo_MockEvents(global::TUnit.Mocks.MockEngine<global::IFoo> engine) => Engine = engine;
}

public static class IFoo_MockEventsExtensions
{
extension(global::TUnit.Mocks.Mock<global::IFoo> mock)
{
public IFoo_MockEvents Events => new(global::TUnit.Mocks.MockRegistry.GetEngine(mock));
}

extension(IFoo_MockEvents events)
{
public global::TUnit.Mocks.EventSubscriptionAccessor Something
=> new(events.Engine, "Something");
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
file sealed class IFooMockImpl : global::IFoo, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject
{
private readonly global::TUnit.Mocks.MockEngine<global::IFoo> _engine;

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; }

internal IFooMockImpl(global::TUnit.Mocks.MockEngine<global::IFoo> engine)
{
_engine = engine;
}

private global::System.EventHandler<string?>? _backing_Something;

public event global::System.EventHandler<string?>? Something
{
add { _backing_Something += value; _engine.RecordEventSubscription("Something", true); }
remove { _backing_Something -= value; _engine.RecordEventSubscription("Something", false); }
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal void Raise_Something(string? e)
{
_backing_Something?.Invoke(this, e);
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public void RaiseEvent(string eventName, object? args)
{
switch (eventName)
{
case "Something":
{
Raise_Something((string?)args!);
break;
}
default:
throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock.");
}
}
}

file static class IFooMockFactory
{
[global::System.Runtime.CompilerServices.ModuleInitializer]
internal static void Register()
{
global::TUnit.Mocks.MockRegistry.RegisterFactory<global::IFoo>(Create);
}

internal static global::TUnit.Mocks.Mock<global::IFoo> Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs)
{
if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IFoo' does not support constructor arguments, but {constructorArgs.Length} were provided.");
var engine = new global::TUnit.Mocks.MockEngine<global::IFoo>(behavior);
var impl = new IFooMockImpl(engine);
engine.Raisable = impl;
var mock = new IFooMock(impl, engine);
return mock;
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated
{
public static class IFoo_MockMemberExtensions
{
public static void RaiseSomething(this global::TUnit.Mocks.Mock<global::IFoo> mock, string? e)
{
((global::TUnit.Mocks.IRaisable)global::TUnit.Mocks.MockRegistry.GetEngine(mock).Raisable!).RaiseEvent("Something", (object?)e);
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks
{
public static class IFoo_MockStaticExtension
{
extension(global::IFoo)
{
public static global::TUnit.Mocks.Generated.IFooMock Mock(global::TUnit.Mocks.MockBehavior behavior = global::TUnit.Mocks.MockBehavior.Loose)
{
return (global::TUnit.Mocks.Generated.IFooMock)global::TUnit.Mocks.Mock.Of<global::IFoo>(behavior);
}
}
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#nullable enable

namespace TUnit.Mocks.Generated;
Loading
Loading