VerifyJson throws PlatformNotSupportedException in AOT (Dynamic code generation is not supported)
Description:
When running tests against an AOT build, calling await VerifyJson(responseBody); fails with:
System.PlatformNotSupportedException : Dynamic code generation is not supported on this platform.
This appears to come from Verify’s default JSON serialization, which relies on dynamic code generation (Emit). That path isn’t available under NativeAOT or trimming.
Repro steps:
- Create a test project referencing Verify.Xunit and Verify.Json.
- Enable AOT:
<PropertyGroup> <PublishAot>true</PublishAot> </PropertyGroup> - Run a test:
[Fact] public Task Sample() { var responseBody = "{\"hello\":\"world\"}"; return VerifyJson(responseBody); } - Run under AOT → crash with PlatformNotSupportedException.
how do i "Run under AOT" ?
better yet. can you submit a pull request with a new test project that illustrates the issue
here is a sample that produce the error: https://github.com/7amou3/TestProject
what is the logic behind AOTing and Trimming a test assembly?
ie what is the value of it?
I Trim/AOT tests to simulate production runtime conditions, catching reflection, serialization, and dynamic code issues before the customers do. Here’s the logic behind AOTing and trimming test assemblies:
- Validation of Production Build Compatibility - running tests under the same constraints ensures my code and dependencies are compatible
- Confidence in Linker/Trimmer Configuration - If trimming removes a type I actually need at runtime, the tests will fail
- Ensuring Deterministic Builds - Running tests with the same publish settings as production ensures my binary is what I expect
ie what is the value of it?
@SimonCropp this is something we've been looking at for Sentry as well. Motivation described in this issue:
- https://github.com/getsentry/sentry-dotnet/issues/3930
Since we've already got thousands of tests, it's going to be a bit of a journey to get decent AOT coverage for the codebase. Currently we've got some integration tests that run in Powershell / Pester which build some apps AOT, run these and then run regex expressions on the console output to verify expectations. These tests are pretty painful to build, run and maintain though, so the coverage of these isn't very good and we'd like to have something more comprehensive and easier to maintain for this.
I wrote an article that might be useful, describing some of the initial challenges we ran into building AOT support for Sentry, including the serialisation issues described above. If you read through that post, you'll see there are pros and cons to this... so if you did build something like that into Verify, you might want to make it opt in (or only use that serialisation technique when you know the test library is being compiled AOT).
Actually detecting whether the code has been compiled AOT or not is it's own challenge... at runtime there aren't really any easy ways to do it. You can reliably detect whether Trimming has been enabled, which is probably just as good... since it's the trimming that really messes with reflection and serialisation.
@7amou3 @jamescrosswell given you both seem to have better understanding of this than me, would either of you like to work on a pull request
I’d love to, but I’m not sure where to start. One idea could be to extract the serializer logic and let users choose their preferred default serializer, similar to how Refit does it.
I'm not going to have bandwidth in the near future... the guts of what we did in Sentry (where we have to support both serialisation techniques) is here:
private static void InternalSerialize(Utf8JsonWriter writer, object value, bool preserveReferences = false)
{
#if NET8_0_OR_GREATER
if (JsonSerializer.IsReflectionEnabledByDefault)
{
JitSerialize();
}
else
{
var context = GetSerializerContext(value.GetType(), preserveReferences);
JsonSerializer.Serialize(writer, value, value.GetType(), context);
}
#else
JitSerialize();
#endif
return;
[UnconditionalSuppressMessage("AOT", "IL3050: RequiresDynamicCode", Justification = AotHelper.AvoidAtRuntime)]
[UnconditionalSuppressMessage("Trimming", "IL2026: RequiresUnreferencedCode", Justification = AotHelper.AvoidAtRuntime)]
void JitSerialize()
{
var options = preserveReferences ? AltSerializerOptions : SerializerOptions;
JsonSerializer.Serialize(writer, value, options);
}
}
So at runtime, it will select the appropriate serialiser... and then there are some UnconditionalSuppressMessage attributes that disable the warnings (since we know this code never gets run for AOT applications at runtime).
I'm not sure how easily/well that maps to Verify...
@jamescrosswell i also dont think i will get to this any time soon
@7amou3 do you want to have a go at a pull request?
Running into the same issue. Naively I would ask, can the issue simply be resolve by passing a set of JsonTypeInfo?