Skip to content

TUnit.Mocks: Is<T>(predicate) passes T? for non-nullable T, forcing null guards (CS8602) #7304

Closed

Description

@thomhurst

Description

With nullable reference types enabled, the predicate passed to Is<T>(...) receives T? even when T is a non-nullable reference type. Every predicate that dereferences its argument then needs a null guard to avoid CS8602, which makes the common case noisy:

mock.Save(Is<User>(u => u.Name == "Alice"));              // CS8602: Dereference of a possibly null reference.
mock.Save(Is<User>(u => u is not null && u.Name == "Alice")); // compiles

During a Moq migration of a large test suite this needed a null guard in a few dozen predicates. Moq's It.Is<User>(u => u.Name == "Alice") passes User, not User?.

Repro (TUnit.Mocks 1.76.45, net10.0, <Nullable>enable</Nullable>, warnings as errors)

public record User(string Name);
public interface IRepo { void Save(User user); }

var mock = Mock.Of<IRepo>();
mock.Save(Is<User>(u => u.Name == "Alice")).WasCalled(); // CS8602

Expected

When T is a non-nullable reference type, the predicate parameter is T. Is<User?>(u => ...) gives the nullable form when a test wants to handle null. Both forms pass null through at runtime and preserve existing predicate exception propagation; use an explicit null guard to make null a non-match.

Updated the null expectation following the repeated review concern on #7405: generic nullable annotations cannot distinguish the two forms at runtime. Preserving exception propagation matches the existing implementation and avoids hiding predicate bugs. The annotation fix remains the requested ergonomic improvement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: edge case, mode divergence, flaky test, or workaround existsbugSomething isn't workingin-progressWork is in progress

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions