NSubstitute icon indicating copy to clipboard operation
NSubstitute copied to clipboard

Error mocking internal members of Trimmable assemblies on Android

Open jamescrosswell opened this issue 1 year ago • 4 comments

Describe the bug Tests that use NSubstitute to mock internal members of Trimmable assemblies fail when run on Android, with the following error:

System.ArgumentException : Can not create proxy for type Sentry.IPing because it is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] attribute, because assembly Sentry is strong-named. Arg_ParamName_Name, additionalInterfacesToProxy]]

If we set <IsTrimmable>false</IsTrimmable> in the .csproj file of the code being tested then the test pass.

To Reproduce

I've provided a minimal reproducable example at:

  • https://github.com/jamescrosswell/device-test-repro

The readme.md file in that repo describes how to run the tests using the included powershell script.

Expected behaviour

We'd like the tests to pass for assemblies marked with IsTrimmable. With the growth of frameworks like MAUI we're seeing more and more people wanting to trim their applications.

Environment:

  • NSubstitute version: 5.1.0
  • NSubstitute.Analyzers version: CSharp 1.0.17
  • Platform: net8.0-android

Additional context We bumped into this in the device tests for the Sentry SDK for .NET. For the time being, I'll look at disabling trimming on the relevant assemblies for the purposes of running these device tests. Ideally we'd be able to run our tests against the same code we ship though and we ship with <IsAotCompatible>true</IsAotCompatible> (so implicitly <IsTrimmable>true</IsTrimmable>).

jamescrosswell avatar Oct 22 '24 00:10 jamescrosswell

Thanks for the report and repro @jamescrosswell. If this is being trimmed from the assembly then I'm not sure there is a lot we can do from the NSubstitute side? (Suggestions welcome! I'm not familiar with net8.0-android)

dtchepak avatar Oct 26 '24 09:10 dtchepak

Adding trimming support isn't trivial AFAIK. I'm tagging this as a feature request

304NotModified avatar Oct 28 '24 17:10 304NotModified

A colleague that does some work with this mentioned they used specific excludes (although in the context of linking not testing). They sent me these references:

  • https://stackoverflow.com/q/75672166/906
  • https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/prepare-libraries-for-trimming?pivots=dotnet-8-0

Not sure if that helps at all but figured I pass them along. :bow:

dtchepak avatar Oct 28 '24 21:10 dtchepak

Adding trimming support isn't trivial AFAIK.

I think that's probably the reality... especially given the dependency on DynamicProxy which, as it's name suggests, is all about dynamic code generation.

The only solution that I can imagine is one that leverages Source Generators to create the mock objects, instead of using DynamicProxy. That would likely require a very different API for people to define the mocks though.

jamescrosswell avatar Oct 30 '24 01:10 jamescrosswell