Time Tortoise: Visual Studio 2017 Upgrade

VS2017

This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.

Two weeks ago, Visual Studio 2017 officially launched. Although preview and release candidate versions of VS2017 have been available for almost a year, I have until now been conservatively using VS2015 for my project. But I decided that now is the right time to upgrade. Time Tortoise is still quite small, but it’s getting bigger. And although it can be tricky to work with new tools, VS2017 has had quite a bit of community testing, so I’m not too worried.

Here’s what I found during the upgrade process, which took place over the past week.

Automatic Migration

When VS2017 opens a VS2015 solution for the first time, it tries to do an automatic migration that applies any required changes to project and solution formats. The solution migration is usually simple: just update the VS version number. The chance of project migration success depends on the type of project involved. For Time Tortoise, most of the projects migrated cleanly. But for some of them, the migration report showed the following error message:

Failed to migrate XProj project (project_name)

The projects in question are the three test projects, which I had used special instructions to create, as recommended on the xUnit.net page for .NET Core projects. Those instructions have been updated since I used them, and now just claim that “Visual Studio 2017 includes built-in support” for these types of projects.

Rather than try to debug the automatic migration, I decided to simply re-create the test projects in VS2017, and copy in my existing code. That’s the approach I’ll describe here.

xUnit.net in VS2017

I’m not sure how the “built-in” support for xUnit.net is supposed to work in VS2017 for .NET Core projects, but these are the steps that worked for me. Keep in mind that the Time Tortoise test philosophy is to avoid UWP unit test projects, and keep as much code as possible in testable class libraries.

After some experimentation, I re-created the test projects as follows:

  • Add – New Project – Class Library (Portable).
  • Enter a project name (TimeTortoise.ViewModel.Tests or TimeTortoise.IntegrationTests).
  • Select ASP.NET Core 1.0 and Windows Universal 10.0.
  • Rename the default class file to match the VS2015 version.
  • Copy in code from the VS2015 project file.
  • Add project references.
  • In Project Properties, click Target .NET Platform Standard.
  • Open Project Properties again and select .NET Standard version 1.4.
  • Add NuGet packages.

The process of creating a Portable Class Library and then switching it to a .NET Standard class library matches the steps I used to create the non-test projects (ViewModel, Model, and DAL). The result is a csproj-style project, which is the format that VS2017 uses (instead of the VS2015-era project.json/xproj system) for .NET Core projects.

SDK and Package Upgrades

Along with VS2017, updated releases of other components have also come out this month. For example, Entity Framework Core v1.1.1 was released on March 6. So as part of upgrading to VS2017, I also upgraded a number of other components.

While some components are easily upgraded in the NuGet Package Manager, others require a traditional Windows installation process. For example, when I first tried to run the app after the VS2017 upgrade, I got the following message:

2>Framework: Microsoft.NET.CoreRuntime.1.1/x86, app package version 1.1.24920.0 is not currently installed.

2>Installing missing frameworks…

At that point, execution hung. Apparently this was a problem in VS2015 as well. As suggested in those Stack Overflow answers, the solution is to uninstall old components and then install new ones. In Windows Add/Remove Programs, I found several VS2015 and Preview versions of the .NET Core SDK. Removing those and installing the latest SDK resolved the issue.

As for NuGet packages, I just upgraded all components to the latest released versions.

Unit Test Discovery

The trickiest problem I ran into during the upgrade had to do with running xUnit.net tests. Even after the application was running fine, none of my unit tests showed up in Test Explorer after a build, and no error messages appeared in the Output window to provide clues.

I didn’t find a perfect solution to this problem, but I found an acceptable workaround. First, I discovered that the xUnit.net Console Runner provides a better error message than the Visual Studio runner. So I installed that NuGet package. Then from the Package Manager Console, I changed to the output directory for my integration test project, and ran this:

xunit.console .\TimeTortoise.IntegrationTests.dll

That produced the following error message:

System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in (output directory)

Strange. To see if I could fix the problem manually, I copied xunit.execution.desktop.dll from my local NuGet package directory into the specified output directory, and ran xunit.console again. The result was this message:

System.IO.FileNotFoundException : Could not load file or assembly ‘xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c’ or one of its dependencies. The system cannot find the file specified.

You can guess what happened when I copied that file: yet another missing DLL. For some reason, Visual Studio was not copying any NuGet DLLs into the output directories for either of my two xUnit.net test projects. So I copied them all manually, which allowed me to run the unit tests. Since they change infrequently, it’s not a big deal to keep them up to date.

The Process Has No Package Identity

After the previous step, all of my tests were passing except for one integration test called ActivitiesList_WithEmptyDatabaseOnDisk_IsEmpty. This is the only one of my tests that exercises the code for reading and writing an on-disk SQLite database. All of my other tests use either in-memory SQLite, or a mock repository that doesn’t depend on a database at all.

Here’s the error I got when running that test:

Message: System.InvalidOperationException : The process has no package identity. (Exception from HRESULT: 0x80073D54)

That’s an obscure message, but fortunately a comment on an old GitHub thread provided the answer: my SqliteContext class contains a database filename without an explicit path. This causes the framework to try to look up Windows.Storage.ApplicationData.Current.LocalFolder.Path, the default location for the database file. This worked fine in my VS2015 solution, but apparently one of the upgrades has changed something, and now my test process has trouble looking up that folder.

The solution I chose is yet another example of dependency injection: I modified the SqliteContext constructor to accept a path. In the UWP application, I pass through the value of LocalFolder.Path. In the integration test, I use Path.GetTempPath(). Now the real application and the integration test both have an appropriate place to store their respective copies of TimeTortoise.db.

The Result

After a week or so of part-time effort, my project is now back to a stable state, but it now uses Visual Studio 2017, .NET Core 1.1.1, Entity Framework Core 1.1.1, EF Core Tools 1.1.0, UWP 5.3.1, Moq 4.7.1, .NET Standard 1.6.1, and xUnit.net 2.2.0.

I’m not explicitly using any new features from these updates, but it’s good to be on the latest released technology as I prepare to add more features and turn my project into a useful time tracking tool.

(Image credit: Betsy Weber)