I was working unit and functional tests for a project that had a database using EntityFramework. It had multiple migrations that we had applied over time. These migrations are placed as code in our project under a Migrations folder. As I was building and running unit tests, I saw large block of code that we were not covering because it was generated code from the EF migrations.
Of course, I could have just ignored those lines of code while working on other unit tests. But that causes a lot of visual noise and makes it harder to find areas that are truly missing tests. So, I started looking for ways to keep the Migrations out of the coverage results.
Option 1: EntityFramework Should Do the Right Thing
Ideally, EntityFramework should generate migration classes with the [GeneratedCode] or [ExcludeFromCodeCoverage] attributes, so it would automatically be ignored from my code’s coverage results. It really should use the [GeneratedCode] attribute since this is generated code, but since it doesn’t, we have to go on to other options.
Option 2: Use [ExcludeFromCodeCoverage] Attribute
I can manually add [ExcludeFromCodeCoverage] to all of the classes within the Migration namespace. With a handful of migrations this works pretty well, but it becomes very cumbersome when projects go through many rounds of migrations.
using System.Diagnostics.CodeAnalysis;
namespace YourApp.Migrations
{
[ExcludeFromCodeCoverage]
public partial class InitialCreate : Migration
{
// ...
}
}
Even after adding all of the attributes to existing migration classes, as new EntityFramework migrations are applied, we will need to remember to add the attribute to the new classes or start seeing missing code coverage again.
For broader impact, I could add this to a Migration base class, if I had one defined for the project. But we didn’t have that, so I would either have to update every class with a new base class or with the attribute. In this scenario, I went with just placing the attribute as needed.
Option 3 – Use .runsettings File
The preferred way is to automatically exclude the whole Migrations folder while working with Visual Studio Test Explorer or dotnet test with coverage enabled.
1. Let’s create a .runsettings file at the top-level solution folder (named myconfig.runnsettings):
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<ExcludeByAttribute>
<Attribute>System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute</Attribute>
</ExcludeByAttribute>
<ExcludeByFile>
<File>**/Migrations/*.cs</File>
<File>**/Migrations/**/*.cs</File>
</ExcludeByFile>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Not that using wildcards like **/Migrations/*.cs works for deeply nested folders within your source tree.
2. Tell Visual Studio to use the new RunSettings file by going to Test > Configure Run Settings > Select Solution Wide Run Settings File menu item. And selecting the myconfig.runsettings file above.
Or in the dotnet CLI, you can run the following command:
dotnet test --settings myconfig.runsettings
3. Now, let’s rerun our unit tests (with code coverage) in the Test Explorer and see the new results. The results should be much better now and all of the code in the Migrations folder will be ignored.
Note: you may need to restart your testing session or restart Visual Studio for the new RunSettings to get picked up sometimes.
If you’re using ReportGenerator in your CI/CD pipeline, it also honors these exclusions when processing the .coverage or .xml files. So these same RunSettings will shared locally and in automated builds.
Conclusion
If you only have a small set of migration classes and are not adding many more, then manually applying the [ExcludeFromCodeCoverage] attribute is the simplest way to address the problem.
But for most cases, defining a .runsettings file for your project is the best course of action. This keeps all class in the Migrations folder out of our code coverage results. It works even as new migrations are made to the project. Checking in the file means it can be shared across multiple developers. And, since ReportGenerator also honors the RunSettings exclusions, it also works in your CI/CD pipeline.
We went with Option 3 in the project I was working on. Which option do you think you would use?