Why can't my test project target netstandard?
netstandard
is an API, not a platform. Due to the way builds and dependency resolution work today, xUnit.net test projects must target a platform (desktop CLR, .NET Core, etc.) and run with a platform-specific runner application.
Note
This page is mostly for users of xUnit.net Core Framework v1 or v2. With xUnit.net Core Framework v3, test projects are always executables, which means they must by definition target a platform rather than an API. This change formalizes requirements that were previously only implicit, which the rest of this document attempts to explain.
Platforms vs. APIs
When .NET was first shipped, there was no difference between platform and API: you directly compiled your binaries against—and ran with—the desktop CLR assemblies. The easy days. 😄
As .NET started to branch out into other platforms (like Silverlight and Windows Phone), the team decided to address the desire to have a single class library which could support multiple target platforms. Portable Class Libraries (PCLs) were invented, and we saw a difference drawn between reference assemblies and runtime assemblies. PCLs allowed you to pick which platforms you needed to support, and provided reference assemblies that contained the subset of APIs that were available on all the target platforms. When the code ran on the target platform, the runtime assemblies provided the platform-specific implementation of the feature(s) in question.
The important thing to remember here is that "portable" was a concept that only applied to class libraries. It did not apply to applications, which were still required to target a specific platform; meaning, there was no such thing as a "portable console application". It can also be a little confusing with .NET platform vs. operating system platform: being "cross platform" here means supporting multiple CLR implementations, not necessarily multiple operating systems. You can target a single .NET platform (.NET Core) which runs on multiple operating system platforms (Windows, Linux, macOS, etc.), or many .NET platforms (Desktop CLR, UWP) which run on a single operating system platform (Windows).
As time passed, the limitations of PCLs became apparent (a topic outside the scope of this document). The key piece of information is that netstandard
is essentially an improvement upon portable class libraries; and like PCLs, they represent API sets that support multiple platforms (for more information, see ".NET Standard").
Your unit tests always run on a platform. In our comparison, they are essentially applications, except that they rely on a shell "launcher" (what we call a Test Runner) to get them running. xUnit.net contains many test runners, which support a variety of platforms (some only support one platform, whereas others support many platforms).
In order to correctly build and run, your unit tests must target a specific platform, just like a console or GUI application does. And, just like a console or GUI application, if you want your tests to be able to run on several platforms, you should use multi-targeting to compile them against several platforms.
Build system requirements
The requirement that your unit tests target a specific platform is not just an arbitrary rule; the build system needs to know what your target platform is in order to make your code ultimately executable. When you build a PCL or a netstandard
class library, the decision on how to make your code runnable comes later, after it's linked into the final application (which must target a platform). Since you don't link your unit tests directly into the runners, the build system needs to know what platform you're going to run on in order to generate the correct behavior and artifacts.
For example, desktop CLR projects need to have their dependencies copied locally; .NET Core applications need to get a .deps.json
file generated by the build system with the list and version of those dependencies, so they can be loaded at runtime. Neither of these steps take place if you target PCL or netstandard
, which makes your code un-runnable in a lot of different scenarios.