dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.2k stars 1.75k forks source link

Unit test projects can't reference Maui multi target projects #3552

Closed JohnHDev closed 1 year ago

JohnHDev commented 2 years ago

Description

With the new design for multi target projects, we need to be able to unit test ViewModels stored in Maui projects.

We could move ViewModels into pure .net 6 projects, but dependencies that are defined in other multi target projects with platform specific implementations can't be mocked regardless without also moving the interfaces for those dependencies into .net 6 projects. That largely negates the positives of multi target projects if we have to create other projects just to contain the Interface definitions and view models.

Steps to Reproduce

Create a .NET Maui solution Create a ViewModel to the .NET Maui project. Create a unit test project and add a reference to the .NET Maui project.

Version with bug

Preview 10 (current)

Last version that worked well

Unknown/Other

Affected platforms

I was not able test on other platforms

Affected platform versions

All

Did you find any workaround?

Create another .net 6 project for view modals and interfaces that the tests and .net maui projects can reference. That just doesn't scale well.

Relevant log output

No response

devmikew commented 2 years ago

Just some thoughts!

In the pure MVVM world your view models should not have references to UI components. This is so they can be unit tested!!

Which is your problem here. So what other references do you have?

Also it is possible to create another testing project which just has file links to the view models. And then run your unit tests against this.

You could also exclude other references by #if in the files and setting a TEST define in the unit test project.

JohnHDev commented 2 years ago

@devmikew I agree, view models should not have references to UI components. Mine don't, they just happen to live in the same project as the views. They don't have to, as I mentioned. But I like to keep views and models organised.

Then consider platform specific implementations for any dependencies that view models might need, lets say a ICalendarRepository with a concrete implementation for each platform. Lets add a Calendars Maui project with the actual platform specific code, the interface definition can also be in the same project. You can't then mock that interface in a test because I can't reference the Maui project from a test project. I can create a separate project just for the interface, but that feels like massive overkill.

devmikew commented 2 years ago

The ICalendarRepository seems to be a data/business layer.

Couldn't you add a net6.0 target framework for this (the repository) project and in that target framework implementation do nothing. Normally you would only target android/ios/windows in this project as these are the real runtime platforms.

JohnHDev commented 2 years ago

Not sure I follow your suggestion, concrete implementations of ICalendarRepository would be to access iOS EKEvents, Android calendar, Windows Appointments etc in a generic way. It isn't business logic, but it is native data access.

The platform specific implementation of ICalendarRepository is DI'ed into the VM constructor. The logic is held in the VM. The VM needs to be unit tested and I would need to mock ICalendarRepository (and any other dependencies it has). I can't though as the unit test project can't access the multi target library Maui projects.

As I said, I could move the interfaces into a .net 6 project, I just don't think I should have to.

devmikew commented 2 years ago

Sorry, but what I tried to say, was to provide an implementation for net-6.0. This implementation does not need to do anything except return a valid result.

Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: JohnHDev @.> Sent: Friday, November 26, 2021 9:07:31 AM To: dotnet/maui @.> Cc: devmikew @.>; Mention @.> Subject: Re: [dotnet/maui] Unit test projects can't reference Maui multi target projects (Issue #3552)

Not sure I follow your suggestion, concrete implementations of ICalendarRepository would be to access iOS EKEvents, Android calendar, Windows Appointments etc in a generic way. It isn't business logic, but it is native data access.

The platform specific implementation of ICalendarRepository is DI'ed into the VM constructor. The logic is held in the VM. The VM needs to be unit tested and I would need to mock ICalendarRepository (and any other dependencies it has). I can't though as the unit test project can't access the multi target library Maui projects.

As I said, I could move the interfaces into a .net 6 project, I just don't think I should have to.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/dotnet/maui/issues/3552#issuecomment-979508294, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AKBBNIJ3RZD4S75USOEGJ5DUN26THANCNFSM5IZJFLGA. Triage notifications on the go with GitHub Mobile for iOShttps://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Androidhttps://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

JohnHDev commented 2 years ago

I am talking about mocking ICalendarRepository in a test, it has nothing to do with concrete implementations. The issue is the Maui project cannot be referenced from the unit test project. Adding a .net 6 implementation doesn't help.

VladislavAntonyuk commented 2 years ago

Please see how it is implemented in Community toolkit https://github.com/CommunityToolkit/Maui/blob/main/src/CommunityToolkit.Maui.UnitTests/CommunityToolkit.Maui.UnitTests.csproj

JohnHDev commented 2 years ago

@VladislavAntonyuk thanks, I have updated my project to match (including removing nunit and adding xunit) but Im not seeing any difference. What is the magic ingredient with that?

VladislavAntonyuk commented 2 years ago

could you share your simple project to reproduce the issue? most likely you have some dependencies which cannot be resolved

JohnHDev commented 2 years ago

UnitTestExample.zip

Here is a very simple example, new Maui solution, added a MainPageModel, added a unit test project, added reference to the maui project in the unit test project, it is not compatible. The unit test project would contain tests for the MainPageModel but obviously can't get that far.

VladislavAntonyuk commented 2 years ago

I see, you try to add executable project to unit test lib. you can only add Maui Library project. image

JohnHDev commented 2 years ago

Its a Maui project that also generates the apps, so it really should not make any difference.

Ok, so try this one: UnitTestExample.zip

I have moved the MainPageModel into its own .net 6 project, and added a reference to an interface held in a new Maui project. It not build because .net 6 projects can't reference .net maui projects.

As I said, I can fix that by moving the ICalendarRepository into a separate .net 6 project, but we shouldn't have to.

VladislavAntonyuk commented 2 years ago

you are doing it wrong. you are trying to add maui library to net6.0 library. it is not correct. the path should be: net6.0=>maui library=>maui executable

VladislavAntonyuk commented 2 years ago

UnitTestExample.zip

JohnHDev commented 2 years ago

That is exactly what I said I could do, but shouldn't have to. The solution you provided is to have another project containing all the unrelated interfaces in 1. I said right at the top that that isn't ideal, and is a poor design.

What if I wanted to create a nuget for a Maui project? It would need 2 project files, 1 for the implementation, and 1 just for the interfaces. That is certainly possible, but imo poor design.

This is the point I have raised in creating this card.

VladislavAntonyuk commented 2 years ago

You can change your test project to support the same frameworks as your Maui project. In that case it should work. I suppose it is by design if .NET, not only the MAUI

devmikew commented 2 years ago

I have a sample working with the maui project targeting net6.0. This allows the test project to reference the maui project and the repository project (assuming its net6.0). The maui project also references the repository project. You can run tests on the repository project either directly or through the maui project.

However there are some problems:

. i had to do a lot of fiddling to get the maui project to compile on all frameworks.

. I think most of the problems were to do with the project system/build system. Eg, I had to continually close and re-open VS or delete the project.assets.json and restore.

. the maui project just degrades to the view models and whatever helpers are needed.

. so i wondered was it worth it, eg, why not have a vm assembly?

.a zip is attached if your interested.

MauiTest.zip

devmikew commented 2 years ago

Attached is a sample project and tests where the VMs and helpers are linked into another project. MauiAppLinked.zip

JohnHDev commented 2 years ago

@devmikew thank you for the sample, but I don't see how that would work, you don't have platform specific implementations for the repository, which means it isn't using MAUI at all other than for the UI.

realZhangChi commented 2 years ago

Maybe you should use device test instead of unit test. See this repository .

devmikew commented 2 years ago

@JohnHDev.

The projects I sent only provide the scaffolding for a possible implementation. Eg:

  1. The repository project has different packages references for windows and android. You could also provide one for net6.0 testing.
  2. Also the repsoitory.cs as a partial method which has to be provided for each platform.
  3. For testing purposes, it is how you provide stubs for testing the repository.
  4. The vmonly project shows how to do a testing project where you link to the VMs.
JohnHDev commented 2 years ago

@devmikew thanks, I appreciate your efforts! When multi platform projects were first suggested for Maui, this was a scenario that was brought up and discussed. Unit testing of VMs should be as simple as mocking the dependencies, calling the VM constructor, and testing. We shouldn't have to jump through hoops for this. At the moment I have worked around it by having any common code in net 6 projects. So for example, if I have a Maui.Calendars project with the platform implementations, I also have a Calendars.Common project with the interfaces. Calendars.Common doesn't have any references to Maui, and so can easily be mocked.

Imo, .net 6 projects should be able to reference .net maui projects, in so far as being able to only reference anything in those projects that are not in the platforms folder. This is a much cleaner and simpler design and makes mocking and unit testing a breeze.

devmikew commented 2 years ago

@JohnHDev I'm not sure what else to do re unit testing a maui app.

It is obvious that it can be done, 'like elephants mating'.

I think the only easy, and testable way, is for Maui to target net6.0. I imagine this would be non-trivial. Eg, it would require a lot of refractoring and split up of the maui assembly into different components.

Maybe that is not a bad thing, as how many developers in the future are going to be facing the same struggles.

VladislavAntonyuk commented 2 years ago

@devmikew Maui can't target net6.0. It doesn't make any sense. Net6.0 is like netstandard (like abstract class), which only describes what you can do in all platforms. For specific implementation you target specific framework like met6.0-Android, which gives you access to Android api. I suppose only UITest project can reference Maui.

devmikew commented 2 years ago

After a little inspection, maui does target net6.0. It seems to have most of the code compiled, however there is a broken reference to Microsoft. Maui Graphics.

So I'm going to dog a bit deeper over the weekend.

trewise commented 2 years ago

Hey @devmikew, did you resolve a more straightforward solution to performing unit test on a MAUI project?

PineYi commented 2 years ago

Verified Repro with Android 11. Repro Project is available: #3552.zip

Axemasta commented 2 years ago

As far as I understand it, we can't reference maui projects directly we instead have to move all of our testable logic into a net6.0 class library and test that project instead?

Will this be changed to feature a fully mockable maui just like we had Xamarin.Forms.Mocks where we could run the virtual ui without needing a platform backing it? (mimicking the tooling support we had in forms)

StepKie commented 2 years ago

I just found this issue after much digging. Unfortunately, all the samples that can be found online are either

a) libraries for .NET MAUI (like the CommunityToolkit) which target net6.0 in addition to net6.0-android,net6.0-ios etc. b) normal MAUI sample apps from Microsoft (Weather TwentyOne, Dotnet Podcasts) which do NOT have net6.0 in their target frameworks, but also do not have a UnitTest project in their solution. This is also what the MAUI dotnet new template produces.

I am just looking for a simple way to UnitTest my MAUI app (net6.0-android,net6.0-ios). I want to use xUnit with a TargetFramework of .net6.0, but that does not work ("bla is not compatible with .net6.0"). Adding net6.0 to the MAUI project gives "Project does not contain a static 'Main' method suitable for an entry point.

So this is kind of like a Catch-22 situation, and I still don't see the solution here. I still have to see a working example anywhere online. If somebody can point me to something (for a real app, not a class library/nuget package like XCT), I would be very grateful.

To clarify: This is my MAUI .csproj:

<PropertyGroup>
        <TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
        <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
        <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
        <!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
        <OutputType>Exe</OutputType>
        <RootNamespace>TestApp</RootNamespace>
        <UseMaui>true</UseMaui>
        <SingleProject>true</SingleProject>
        <ImplicitUsings>enable</ImplicitUsings>

and this is from the xUnit project referencing the above:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <UseMaui>true</UseMaui>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
    <PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
    <PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.3" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.1.2">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\TestApp\TestApp.csproj" />
  </ItemGroup>

</Project>

This obviously does not work. But which setup will? With Xamarin it was no problem because the app was simply targeting .netstandard2.1, which was compatible with the xUnit targeting .net6.0. But what now after the arrival of MAUI?

Axemasta commented 2 years ago

@Hottemax I've been able to work around the issue by moving domain logic away from the Maui project and into seperate class libraries. Its definitely better for enterprise applications & having a healthier architecture but really annoying for quick projects / simple projects.

Possibly adding net6.0 as a target to the maui app might work but then you basically have a dead target thats just for testing which sort of defeats the point. Forms was nice targetting net standard because you could essentially run it without a host architecture (iOS / Android / UWP) and run the entire virtual ui layer from a unit tests with Xamarin.Forms.Mocks. I'm fairly sure the maui targets (ie net6.0-ios) needs to be run from a real platform ie simulator / iphone. Previously we couldn't run unit tests that targetted iOS without using the NUnit adapter for iOS and running the test host within an app.

My work in progress for splitting out unit tests is here if you're interested, real basic but at least lets me write tests for code used in my maui app...

StepKie commented 2 years ago

@Axemasta, I appreciate your response and the link you provided, thank you very much! Yet, for me that is not a totally viable solution:

1) I really want to test my ViewModel functionality as well. 2) I feel that splitting out domain logic again defeats the purpose of the MAUI single-project approach.

JohnHDev commented 2 years ago

@all I have actually had some success with this. I am able to unit test Maui library projects. So all of my views and models are now contained in Maui library projects, and the startup app project is almost entirely empty.

Can someone try that and update this thread with your findings. I don't know why Maui library projects work, but Maui app projects do not, but I'd like to find out.

Axemasta commented 2 years ago

@ALL I have actually had some success with this. I am able to unit test Maui library projects. So all of my views and models are now contained in Maui library projects, and the startup app project is almost entirely empty.

Can someone try that and update this thread with your findings. I don't know why Maui library projects work, but Maui app projects do not, but I'd like to find out.

That is the approach I've taken in my playground apps and will continue to take going forwards.

For smaller projects its a bit of a bug bear since theres a big more to maintain but for bigger projects its basically mirroring best practises in terms of separating concerns (views, domain logic, dal, api logic etc). The reason the class libraries work I suspect is because theyre targetting net6.0 and the maui apps aren't (they're targetting ios, android, mac, windows equivalents).

@Hottemax

  1. I really want to test my ViewModel functionality as well.

You can still do this with my approach, your viewmodels should leave your maui app project and be moved to a class library which contains domain information (since a view model deals with domain logic). Its inadvertently a good practise since MVVM stipulates a viewmodel should not know about a view. Creating a hard boundary of abstraction makes this principle harder to violate since you can't accidentally start writing view specific code in your view models without pulling in extra dependencies.

  1. I feel that splitting out domain logic again defeats the purpose of the MAUI single-project approach.

The single project approach is for targetting platforms. Packages like XCT, XE have been using multitargetting for years to simplify the codebases and make it easier to deliver a package to many platforms. This doesn't eliminate the requirement for abstraction and separation of concerns imo. If you go and read the source code for the .net runtime, you will see extension use of abstracted projects and clean architecture. I think this limitation forces us to adopt cleaner and healthier architecture patterns so whilst its alarming I can't just file new maui + file new nunit project, I'm not complaining about the current solution :)

JohnHDev commented 2 years ago

@Axemasta to clarify, I am referring to Maui library projects which do target the platforms, but also support net6.0 which is why I can unit test the VMs.

StepKie commented 2 years ago

@Hottemax

  1. I really want to test my ViewModel functionality as well.

You can still do this with my approach, your viewmodels should leave your maui app project and be moved to a class library which contains domain information (since a view model deals with domain logic). Its inadvertently a good practise since MVVM stipulates a viewmodel should not know about a view. Creating a hard boundary of abstraction makes this principle harder to violate since you can't accidentally start writing view specific code in your view models without pulling in extra dependencies.

@Axemasta I tried out your approach because I think the reasoning makes sense, although I think it is overkill for my smallish project and deviates from all the samples provided by the MAUI team at Microsoft.

Be that as it may, I started extracting a library project and then I realized that some of my ViewModels have a [QueryProperty] annotation from the Microsoft.Maui.Controls namespace. So I really don't want to pull in Microsoft.Maui.* dependencies in the "library" project I just extracted.

So I think this introduces just more headaches than it solves for me, because I figure this would be just the start of all kind of other issues rearing their head ... :(

anpin commented 2 years ago

I was able to successfully run xunit tests against MAUI project using a copy of DeviceTests.Runners. Basically I had main project with all MAUI stuff and additional net6.0 target, test project with xunit tests targeting net6.0 only referncing main project, and the runner project referencing both projects and all nugets. I had to add separate configuration to omit compilation of platform implementations of MauiApplication in the main project otherwise it would throw exception in the runner as MAUI project can't have two MauiApplication instances. Then I've used xharness to run the tests as such

dotnet clean ./MySolution.Tests.Runner/MySolution.Tests.Runner.csproj -c:Debug_Tests -f:net6.0-android
dotnet build ./MySolution.Tests.Runner/MySolution.Tests.Runner.csproj -c:Debug_Tests -f:net6.0-android -p:EmbedAssembliesIntoApk=true
dotnet tool run xharness android test --output-directory=./test_output/net6.0-android/ --app=./MySolution.Tests.Runner/bin/Debug_Tests/net6.0-android/com.myapp.testrunner-Signed.apk --package-name=com.myapp.testrunner

Also works for iOS and Mac Catalyst and dotnet test, but not for Windows target (error NU1201: Project MySolution.Tests is not compatible with net6.0-android31.0 (.NETCoreApp,Version=v6.0) / win-x64. which I can probably mitigate by commenting out all targets except net6.0-windows and building solution with msbuild)

ismasanchez commented 2 years ago

Is there any plan so it is possible to reference Maui multi target projects for Unit testing? The different solutions mentioned previously are just a workaround in my opinion, it is true that is good practice to separate business logic in another library, but MAUI should be testable as a single project.

deividt commented 2 years ago

@Hottemax

I was able to successfully run xUnit tests against MAUI project adding a compiler constant for net6 target and adding a missing fake entry point for net6 only. Like so:

In the MAUI app .csproj:

<TargetFrameworks>net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<PropertyGroup Condition="$(TargetFramework) == 'net6.0'">
      <DefineConstants>$(DefineConstants);NET6_TARGET</DefineConstants>
</PropertyGroup>

If you try to build the app, will raise an error:

So, to fix that, In the MAUI app App.xaml.cs, Add a fake entry point :

#if NET6_TARGET
public static void Main(string[] args) {}
#endif

Now, the MAUI app is building and you can reference it in the xUnit test project.

The only requirements for the unit test project are:

<TargetFramework>net6.0</TargetFramework>
<UseMaui>true</UseMaui>

Keep in mind that I only tested this in a new blank MAUI app, so, I'm not sure about other blockers that may have.

breenbob commented 2 years ago

@deividt I tried your approach just now in a project with ~300 unit tests, many of which are testing view models and controls from a Maui app multitarget project, and it worked great! Some tests are even passing 😝 I initially had an issue running the Maui app project on Windows after adding net6.0 as a target framework, even though it would build fine: The recipe file "myapp.build.appxrecipe" does not exist. You may need to build your project

Building would not resolve. Curious to know if your Maui app project still deploys OK, even after uninstalling and doing a clean/rebuild?

It looked to me like it was trying to launch the app from the bin/debug/net6.0 folder instead of the net6.0-windows10.0.19041.0 folder.

So I moved the net6.0 target framework to be last, after mac catalyst, reloaded the csproj, and it seemed to kick into gear and deployed the WinUI app successfully. Thanks for sharing this!

deividt commented 2 years ago

@breenbob, I'm happy to know that this approach worked. 😄

Though I just did some tests and I don't have real apps running, even didn't test in windows, so I don't have enough information to give you about the deployment.

Now that you mentioned about the order of the target framework. I've also noticed something similar, if I put it in different places (e.g. after the definition of OutputType) , won't build or will give different errors.

Another approach that I was trying, was just adding the net6 target conditionally, but I didn't have success with that. Something like:

<TargetFrameworks Condition="$(OutputType) == 'Library'">$(TargetFrameworks);net6.0</TargetFrameworks>

jfversluis commented 2 years ago

Hey everyone, thanks for all the input! We definitely need to make this story better. A while back I got this question from someone and I came up with this: https://github.com/jfversluis/MauiUnitTestSample the changes needed are in the 2 csproj files and marked with a comment that starts with xUnit. I think it's 3 places in total. Does that help at all?

I think it's pretty similar to what @deividt has been doing

deividt commented 2 years ago

Hey @jfversluis, your approach worked in my project too. Thanks for sharing. Now I see why my other approach didn't work. I was trying to add .net6 target conditionally, instead of adding the OutputType.

ericnev commented 2 years ago

This may or may not be related, but after applying the changes as described by @jfversluis , and firing up my (NUnit) tests, it barks at calls to FileSystem with a Microsoft.Maui.ApplicationModel.NotImplementedInReferenceAssemblyException. I'd have thought adding useMaui tags to the csproj would do the job, but not so much.

Axemasta commented 2 years ago

This may or may not be related, but after applying the changes as described by @jfversluis , and firing up my (NUnit) tests, it barks at calls to FileSystem with a Microsoft.Maui.ApplicationModel.NotImplementedInReferenceAssemblyException. I'd have thought adding useMaui tags to the csproj would do the job, but not so much.

@ericnev This is expected behaviour since your unit tests are targetting net6.0 instead of your platforms which target net6.0-ios, net6.0-android etc. There is no 'shared' implementations of the essentials api's since by nature they are a way of performing native / platform from shared code.

If you read the source code for FileSystem you will see on ios some lovely implementations and in the .netstandard some exceptions being thrown, since there is nothing to implement.

You will need to use the interfaces for the FileSystem (IFileSystem) and inject it into the class you want to test in order to be able to mock out the interface with something like Moq.

This approach was what we used in Xamarin with the Xamarin.Essentials.Interfaces package that would allow us to properly test our classes that needed to reference Xamarin.Essentials apis 😄

ericnev commented 2 years ago

This may or may not be related, but after applying the changes as described by @jfversluis , and firing up my (NUnit) tests, it barks at calls to FileSystem with a Microsoft.Maui.ApplicationModel.NotImplementedInReferenceAssemblyException. I'd have thought adding useMaui tags to the csproj would do the job, but not so much.

@ericnev This is expected behaviour since your unit tests are targetting net6.0 instead of your platforms which target net6.0-ios, net6.0-android etc. There is no 'shared' implementations of the essentials api's since by nature they are a way of performing native / platform from shared code.

If you read the source code for FileSystem you will see on ios some lovely implementations and in the .netstandard some exceptions being thrown, since there is nothing to implement.

You will need to use the interfaces for the FileSystem (IFileSystem) and inject it into the class you want to test in order to be able to mock out the interface with something like Moq.

This approach was what we used in Xamarin with the Xamarin.Essentials.Interfaces package that would allow us to properly test our classes that needed to reference Xamarin.Essentials apis 😄

I can move this discussion to somewhere else if it's not the appropriate venue, but.. there's a bit of a catch-22 here. If I target net6.0, I get that exception, as I (somewhat) expected, but if I target, for example, net6.0-windows10.0.19041.0, the tests can't find the framework, with an exception similar to the one found in https://github.com/dotnet/maui/pull/3017

I should be clear that I'd prefer to run the tests using net6.0-windows10.0.19041.0, and not mock out FileSystem.

Microsoft.VisualStudio.TestPlatform.ObjectModel.TestPlatformException: Testhost process exited with error: You must install or update .NET to run this application.
App: C:\Users\eneville\source\repos\helios-host-application\Convergent.App.Services.Tests\bin\Debug\net6.0-windows10.0.19041.0\testhost.exe
Architecture: x64
Framework: 'Microsoft.Maui.Core', version '**FromWorkload**' (x64)
.NET location: C:\Program Files\dotnet
aritchie commented 2 years ago

For those looking for a way to unit test their viewmodels by referencing a .NET6 version of the MAUI single project, here is a few csproj tricks to help you out. It isn't perfect, but will get you most of the way

https://gist.github.com/aritchie/fa5be94f831f0fe9aaefd22b029915d7

rogersm-uwosh commented 2 years ago

Am I reading this right? It's not possible, through Visual Studio directly, to create unit tests for .NET MAUI projects? That we need to dive into the .csproj to make things work (or just extract the model classes and put them in a separate, console-based (say) application)?

codingL3gend commented 2 years ago

i wrote a blog post about this here https://codingistherapeutic.com/2022/10/19/unit-testing-a-net-maui-app-with-platform-specific-code/.

Also i did recently see with a new .net maui application that the Unit Testing project doesnt have the true and it built and ran test without an issue, even referencing ViewModels. so there could be some potential there

ismasanchez commented 1 year ago

Again, those are all workarounds in order to solve a design fault. If you add net6.0 in the target frameworks, whenever you start using implementation specific code in your project, you enter into the nightmare realm of #if #else compilator hell. No plans to address this issue with the launch of net7.0?

aritchie commented 1 year ago

@ismasanchez I don't see this as a design fault at all. MAUI gives you the standard interfaces on net60, not just the platforms. The xaml fails to compile properly right now, it is easy to move that out of the with csproj. If you think that this is truly a design flaw, how would you solve this?

You really 3 options

1) unit test with a device runner like https://github.com/shinyorg/xunit-maui

2) Separate your standard code into a separate library. This is a better scenario for larger teams anyhow.

3) Realizing that my post that you suggest is a workaround, is essentially what MSFT is likely to end up doing to a large degree because that is the nature of single multitargeted projects. You also can keep platform specific code out of the way with interfaces and dumping it in the platforms folder, thus saving you from if defs.