{"id":369,"date":"2021-05-26T11:30:52","date_gmt":"2021-05-26T18:30:52","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/?p=369"},"modified":"2021-05-26T11:56:56","modified_gmt":"2021-05-26T18:56:56","slug":"winui-desktop-unit-tests","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/winui-desktop-unit-tests\/","title":{"rendered":"WinUI Desktop Unit Tests"},"content":{"rendered":"<h2>Unit Tests<\/h2>\n<p>From the beginning, the Universal Windows Platform has supported Unit Testing via multiple test frameworks. Now with WinUI Desktop being generally available and given that you can&#8217;t do test-driven development without a test framework, a new way to create tests for it was required.<\/p>\n<p>In this blog post, we&#8217;ll jump straight into how to create a Unit Test project for WinUI Desktop, using MSTest!<\/p>\n<hr \/>\n<h2>MSTest<\/h2>\n<blockquote><p>I&#8217;m not going into the details of why you need tests in your project, since there are plenty of articles on <a href=\"https:\/\/docs.microsoft.com\/visualstudio\/test\/unit-test-basics\">why unit testing is a good idea<\/a>, but a quick recap, for context, is always a good idea.<\/p><\/blockquote>\n<p>MSTest is the Microsoft Test Framework and it allows developers to create tests that are fully integrated with Visual Studio. <a href=\"https:\/\/github.com\/microsoft\/testfx\">MSTest v2<\/a> is a fully supported, open source, and cross-platform implementation of MSTest that supports .Net Framework, .Net Core\/.NET 5, ASP.Net, UWP, and now WinUI.<\/p>\n<p>Since WinUI Desktop apps are executed as MSIX packaged .NET 5 applications (<a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/apps\/winui\/winui3\/release-notes\/winui3-project-reunion-0.5-preview\">more details here<\/a>), creating tests for it is very similar to creating tests for .NET 5. The biggest difference is the same as the one we had for UWP apps: the UI Thread.<\/p>\n<p>Given that WinUI Desktop is still very recent, we don&#8217;t yet have templates in <a href=\"https:\/\/github.com\/microsoft\/ProjectReunion\">Project Reunion<\/a> to create Unit Tests based on WinUI3, so I will walk you through the steps required to make a Unit Test project based on a WinUI Desktop app. The process is similar to how to do <a href=\"https:\/\/docs.microsoft.com\/visualstudio\/test\/walkthrough-creating-and-running-unit-tests-for-windows-store-apps\">unit tests for UWP apps<\/a>. Lets get started!<\/p>\n<hr \/>\n<h2>File, New, Project<\/h2>\n<p>With the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ProjectReunion.MicrosoftProjectReunion\">Project Reunion 0.5.7 VSIX<\/a> installed, lets create a new packaged WinUI Desktop blank app:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-NewProject.png\" alt=\"New WinUI Desktop App\" \/><\/p>\n<p>I&#8217;ve named mine as <code>WinUIDesktopUnitTestApp1<\/code>, because why not?<\/p>\n<p>Remove the StackPanel and it&#8217;s content from the <code>MainWindow.xaml<\/code> file, and replace it with an empty Frame:<\/p>\n<pre><code class=\"language-xml\">&lt;Window\r\n    x:Class=\"WinUIDesktopUnitTestApp1.MainWindow\"\r\n    xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\r\n    xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\r\n    xmlns:d=\"http:\/\/schemas.microsoft.com\/expression\/blend\/2008\"\r\n    xmlns:local=\"using:WinUIDesktopUnitTestApp1\"\r\n    xmlns:mc=\"http:\/\/schemas.openxmlformats.org\/markup-compatibility\/2006\"\r\n    mc:Ignorable=\"d\"&gt;\r\n\r\n    &lt;Frame \/&gt;\r\n&lt;\/Window&gt;\r\n<\/code><\/pre>\n<p>Make sure to remove the <code>myButton_Click<\/code> method from the <code>MainWindow.xaml.cs<\/code> file. Both these steps are just cleaning the default template, as we don&#8217;t need that in our unit tests.<\/p>\n<p>Now to the fun part!<\/p>\n<h2>Preview features!<\/h2>\n<p>To leverage Unit Tests in WinUI Desktop, we need to enable a preview MSIX feature, which is turned on by the <code>EnablePreviewMsixTooling<\/code> MSBuild property. It will shift from having two projects (<code>csproj<\/code>+<code>wapproj<\/code>), to having only one project (a single <code>csproj<\/code>). For the time being, this feature is still under preview, so there is no template to help us use it. Nevertheless, it should not be complex to migrate an existing project to use it. Lets go through the steps.<\/p>\n<p>First, lets manually edit the <code>WinUIDesktopUnitTestApp1.csproj<\/code> by double clicking on it in Solution Explorer. Inside the first <code>PropertyGroup<\/code> section, add this:<\/p>\n<pre><code class=\"language-xml\">    &lt;EnablePreviewMsixTooling&gt;true&lt;\/EnablePreviewMsixTooling&gt;<\/code><\/pre>\n<p>We also need to move a few files around. Copy both the <code>Images<\/code> folder, as well as the <code>Package.appxmanifest<\/code> file, from the folder where the <code>wapproj<\/code> is, into the <code>csproj<\/code>&#8216;s folder. <em>I recommend you do that through File Explorer, so VS doesn&#8217;t try to alter your csproj file.<\/em> Now, make sure your <code>csproj<\/code> references the copied files, like this:<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\r\n    &lt;AppxManifest Include=\"Package.appxmanifest\"&gt;\r\n        &lt;SubType&gt;Designer&lt;\/SubType&gt;\r\n    &lt;\/AppxManifest&gt;\r\n&lt;\/ItemGroup&gt;\r\n&lt;ItemGroup&gt;\r\n    &lt;Content Include=\"Images\\LockScreenLogo.scale-200.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\SplashScreen.scale-200.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\Square150x150Logo.scale-200.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\Square44x44Logo.scale-200.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\Square44x44Logo.targetsize-24_altform-unplated.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\StoreLogo.png\" \/&gt;\r\n    &lt;Content Include=\"Images\\Wide310x150Logo.scale-200.png\" \/&gt;\r\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<p>You can now completely delete the <code>wapproj<\/code> folder (<code>WinUIDesktopUnitTestApp1 (Package)<\/code>), as well as remove it from your solution. This is all that is needed to use the preview MSIX Tooling. You should have a much cleaner solution, with a single project, similar to this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-SolutionExplorer.png\" alt=\"Solution Explorer with single solution\" \/><\/p>\n<p>If you build and run this app as-is, it should run, but you still can&#8217;t create your tests. Lets now update this project to make it an actual test project. First, add this <code>ItemGroup<\/code> to your <code>csproj<\/code>:<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\r\n    &lt;ProjectCapability Include=\"TestContainer\" \/&gt;\r\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<p>This helps VS understand that this project is a test project. You may notice that the project icon in Solution Explorer will change when you save the <code>csproj<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-SolutionExplorer-TestIcon.png\" alt=\"Solution Explorer showing project with test icon\" \/><\/p>\n<p>We also need the new NuGet packages that are fresh out of the oven!<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\r\n    &lt;PackageReference Include=\"MSTest.TestAdapter\"&gt;\r\n        &lt;Version&gt;2.2.4&lt;\/Version&gt;\r\n    &lt;\/PackageReference&gt;\r\n    &lt;PackageReference Include=\"MSTest.TestFramework\"&gt;\r\n        &lt;Version&gt;2.2.4&lt;\/Version&gt;\r\n    &lt;\/PackageReference&gt;\r\n    &lt;PackageReference Include=\"Microsoft.TestPlatform.TestHost\"&gt;\r\n        &lt;Version&gt;16.10.0&lt;\/Version&gt;\r\n        &lt;ExcludeAssets&gt;build&lt;\/ExcludeAssets&gt;\r\n    &lt;\/PackageReference&gt;\r\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<blockquote><p>Notice the <code>&lt;ExcludeAssets&gt;build&lt;\/ExcludeAssets&gt;<\/code> in the <code>Microsoft.TestPlatform.TestHost<\/code> PackageReference node.<\/p><\/blockquote>\n<p>This will allow us to reference the <code>MSTest<\/code> types, and properly build and run our tests.<\/p>\n<p>We now need to activate our test app, so it runs the tests we need it to run (as compared to just running the app and running all the tests, all the time). Lets edit our <code>OnLaunched<\/code> method from our <code>App.xaml.cs<\/code> file:<\/p>\n<pre><code class=\"language-cs\">using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;\r\n\r\n...\r\n\r\nprotected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)\r\n{\r\n    Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();\r\n\r\n    m_window = new MainWindow();\r\n\r\n    \/\/ Ensure the current window is active\r\n    m_window.Activate();\r\n\r\n    UITestMethodAttribute.DispatcherQueue = m_window.DispatcherQueue;\r\n\r\n    \/\/ Replace back with e.Arguments when https:\/\/github.com\/microsoft\/microsoft-ui-xaml\/issues\/3368 is fixed\r\n    Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(Environment.CommandLine);\r\n}<\/code><\/pre>\n<p>Now the only missing piece is the actual test file, with our tests. Create a new <code>C#<\/code> file with a simple test inside it:<\/p>\n<pre><code class=\"language-cs\">using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\nnamespace WinUIDesktopUnitTestApp1\r\n{\r\n    [TestClass]\r\n    public class UnitTest1\r\n    {\r\n        [TestMethod]\r\n        public void TestMethod1()\r\n        {\r\n            Assert.AreEqual(0, 0);\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>By simply saving this file, you should see that the tests are properly discovered on VS&#8217;s <code>Test Explorer<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-TestExplorer.png\" alt=\"Test Explorer detecting WinUI test\" \/><\/p>\n<p>Right click on the test, and simply run it. This will build the project, deploy your MSIX package, run your tests, and update Test Explorer with the results!<\/p>\n<blockquote><p>Remember that this is all still under preview. Closing VS and opening it again usually helps fixing some issues, so have that in mind if something seems wonky.<\/p><\/blockquote>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-TestExplorer-PassingTests.png\" alt=\"Test Explorer with passing test\" \/><\/p>\n<p>Now that the basics are working, lets create a test that uses the <code>Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer.UITestMethodAttribute<\/code>. This allows you to run code directly in the UI thread of your WinUI3 Window. It is very likely that you will need to do so, given the fact that any code that creates or modifies a UI element usually needs to be executed from the UI thread. A code as simple as <code>var grid = new Grid()<\/code> will throw an exception if you try to run it from a regular <code>TestMethod<\/code>:<\/p>\n<pre><code> TestMethod1\r\n   Source: UnitTest1.cs line 11\r\n   Duration: 44 ms\r\n\r\n  Message: \r\n    Test method WinUIDesktopUnitTestApp1.UnitTest1.TestMethod1 threw exception: \r\n    System.Runtime.InteropServices.COMException: The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD)) For UWP projects, if you are using UI objects in test consider using [UITestMethod] attribute instead of [TestMethod] to execute test in UI thread.\r\n  Stack Trace: \r\n    ExceptionHelpers.ThrowExceptionForHR(Int32 hr)\r\n    _IGridFactory.CreateInstance(Object baseInterface, IntPtr&amp; innerInterface)\r\n    Grid.ctor()\r\n    UnitTest1.TestMethod1()<\/code><\/pre>\n<p>Luckily, the exception is quite clear on what we should do to fix the issue, so just replace the <code>[TestMethod]<\/code> with <code>[UITestMethod]<\/code>, adding a using statement to <code>Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer<\/code>, which will force this test to be executed on the UI thread.<\/p>\n<p>And there you have it:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-content\/uploads\/sites\/61\/2021\/05\/WinUIUnitTests-TestExplorer-PassingTests-UITestMethodAttribute.png\" alt=\"Test Explorer with passing test using UITestMethodAttribute\" \/><\/p>\n<p>This is just the basics of what a simple test can achieve, but more complex test scenarios can be achieved by building up on this project.<\/p>\n<p>Thank for reading, and see you next time!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lets jump straight into how to create a Unit Test project for WinUI Desktop, using MSTest!<\/p>\n","protected":false},"author":40006,"featured_media":1176,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[10,9,35,36,5,37,8,6,7],"class_list":["post-369","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ifdef-windows","tag-desktop","tag-preview","tag-projectreunion","tag-reunion","tag-uwp","tag-win32","tag-windows","tag-winui","tag-winui3"],"acf":[],"blog_post_summary":"<p>Lets jump straight into how to create a Unit Test project for WinUI Desktop, using MSTest!<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/posts\/369","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/users\/40006"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/comments?post=369"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/posts\/369\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/media\/1176"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/media?parent=369"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/categories?post=369"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ifdef-windows\/wp-json\/wp\/v2\/tags?post=369"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}