maui
maui copied to clipboard
Unit test projects can't reference Maui multi target projects
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
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.
@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.
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.
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.
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.
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.
Please see how it is implemented in Community toolkit https://github.com/CommunityToolkit/Maui/blob/main/src/CommunityToolkit.Maui.UnitTests/CommunityToolkit.Maui.UnitTests.csproj
@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?
could you share your simple project to reproduce the issue? most likely you have some dependencies which cannot be resolved
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.
I see, you try to add executable project to unit test lib. you can only add Maui Library project.

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.
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
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.
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
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.
Attached is a sample project and tests where the VMs and helpers are linked into another project. MauiAppLinked.zip
@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.
Maybe you should use device test instead of unit test. See this repository .
@JohnHDev.
The projects I sent only provide the scaffolding for a possible implementation. Eg:
- The repository project has different packages references for windows and android. You could also provide one for net6.0 testing.
- Also the repsoitory.cs as a partial method which has to be provided for each platform.
- For testing purposes, it is how you provide stubs for testing the repository.
- The vmonly project shows how to do a testing project where you link to the VMs.
@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.
@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.
@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.
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.
Hey @devmikew, did you resolve a more straightforward solution to performing unit test on a MAUI project?
Verified Repro with Android 11. Repro Project is available: #3552.zip
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)
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?
@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...