Sonar-dotnet with Unit test details soon to be released!

Hi there, .NET developers!

We will soon release a new version of our .NET analyzers that finally include Unit test details.

This is a change that has been requested for a long time, so we are very happy to finally be able to make it available. This feature supports MSTest, NUnit and XUnit.

Please note this comes with some warnings :

  • We now parse the full unit test report files, and map individual tests. However, by default we do not report on generated code. If you tests are generated, they will not be taken into account. This means the number of reported tests may go down. If you have a quality gate condition on this metric you might encounter a failed quality gate because of that.
  • SonarQube will now calculate the test density metric for .NET tests (which it could not do before). If you had a quality gate condition on this metric, you might encounter a failed Quality Gate (but it is arguably normal, as the value will now be calculated properly for .NET code).
    Please review your quality gate conditions and update them appropriately.

As always, please send us your feedback!

Denis

4 Likes

This is good news.
Is this feature/functionality currently available?
I’m working with C# and our SonarQube Server (Community Edition Version v9.9.8 (build 100196)) which shows the number of unit tests but when I click on the link there’s no results shown.
It would be good to report which Unit Tests pass and which ones fail.
We look forward to having this feature.

Hi, @boblinn

This was released in January, In SonarQube Cloud, SonarQube Server 2025.1 LTA, and SonarQube Community Build 25.1.

Since you are on Community Edition, I would advise to upgrade to a recent Community Build release to benefit from this (and many more new features and improvements since CE 9.9)

Denis

Do you have any screens shots of where SonarQube reports the unit tests?
We upgraded our SonarQube to version ‘Developer Edition v2025.2 (105476)’ and re-ran the analysis multiple times and so far there are no unit tests appearing on SonarQube.
We do see the Code Coverage but no unit tests.
The TestResults folder and file do contain the unit tests.

We’re using ‘dotnet sonarscanner…’ processes using nunit.
Here’s the command: dotnet-coverage collect dotnet test -f xml -o ‘coverage.xml’ --results-directory ./TestResults --logger:“nunit”

Any help or images is appreciated.

Thanks
Bob

Hi there, @boblinn

To see test results in your project, you need to pass in a specific file as described in our documentation. This is different from test coverage.

Once you do that, in the metrics, next to the test coverage metrics, you should see things like number of tests, number of failed tests…

Hope that helps. If you need more pointers, please let me know here.

This is all I see.
There are no test metrics or results shown.
The command I use to generate the TestResults is: dotnet-coverage collect dotnet test -f xml -o ‘coverage.xml’ --results-directory ./TestResults --logger:“nunit”

Test results are captured in the specified folder/file.

I’ve tried specifying only the folder as well as specifying both the folder and file as described in the docs under the sonar.cs.nunit.reportsPaths section.

I’ve tried specifying the parameters in the command line, in the sonar-scanner.properties file and in the SonarQube.Analysis.xml files. No metrics appear on the page.

Under the ‘Measures/Coverage…’ sections of the report it shows 1 Test case even though there are nearly 400.

Your thoughts and patience are appreciated.

@boblinn
Since you seem to be using nunit, you also need to pass in the property /d:sonar.cs.nunit.reportsPaths=./TestResults/*.xml (based on what I see in your dotnet test command) in the begin step of the scanner invocation.

Maybe you could give me the (redacted) content of your pipeline?

Here’s the dotnet sonarscanner commands I use. I first navigate to the root folder of the soution.

  • dotnet sonarscanner begin /k:“xxx” /d:sonar.host.url=“https://xxxxxxx” /d:sonar.login=“xxxxxxxxxxxxxxxx” /d:sonar.scanner.scanAll=false /d:sonar.verbose=true /d:sonar.scanner.truststorePassword=sonarlint /d:sonar.scanner.truststorePath=<my_Windows_home>.sonarlint\ssl\truststore.p12 /d:sonar.cs.vscoverage.reportsPaths=./coverage.xml /d:sonar.cs.nunit.reportsPaths=./TestResults/TestResults.xml
  • dotnet build ./<our_solution>.sln
  • dotnet-coverage collect dotnet test -f xml -o ‘coverage.xml’ --results-directory ./TestResults --logger:“nunit”
  • dotnet sonarscanner end /d:sonar.login=“xxxxxxxxxxxxx” /d:sonar.scanner.truststorePassword=sonarlint

Thank you.

@boblinn thank you.

At first glance this looks right. Could you please add /d:sonar.verbose=true in the begin step, run the pipeline and share the (redacted if need be) logs for the begin step and the end step?

Please find the begin and end debug logs attached as requested.
sonarDebugBegin.txt (8.0 KB)
sonarDebugEnd.txt (2.2 MB)

Thanks
Bob

I’m aware of the entries in the ‘sonarDebugEnd.txt’ file that say “…test cannot be mapped to the test source file. The test will not be included.” I’m unsure if this sheds light on the issue or how to address it.

If it’s helpful here’s an image of the folder structure.

I execute the sonarscanner processes from the <root_folder> which is the ‘sonarDebugFileStructure’ folder in the image.

Here’s what the SonarQubeAnalysis.xml configuration properties file looks like.

<SonarQubeAnalysisProperties  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.sonarsource.com/msbuild/integration/2015/1">
  <Property Name="sonar.host.url">https://xxxxxxx</Property>
  <Property Name="sonar.token">xxxxxxxxx</Property>
  <Property Name="sonar.exclusions">**/*.settings.json, **/MoqResponse.cs</Property>
  <Property Name="sonar.cs.vscoveragexml.reportsPaths">./coverage.xml</Property>
  <Property Name="sonar.cs.nunit.reportsPaths">./TestResults/TestResults.xml</Property>
</SonarQubeAnalysisProperties>

Indeed, most of the tests are dropped due to not being able to map them back to source.

I’ll flag it for more experts eyes, because I’m not sure what is happening here.

Hello @boblinn!

One quick thing that I noticed in your begin step, is that you are passing the coverage like so
/d:sonar.cs.vscoverage.reportsPaths=./coverage.xml.
However later I see you use the dotnet coverage tool which actually requires that you pass in the begin step the coverage like /d:sonar.cs.vscoveragexml.reportsPaths=./coverage.xml (the only difference is that there’s one xml suffix on the third part of the parameter name, however without this it should not work see docs).

Could you please do a quick test if that solves the problem?

Meanwhile I’ll check also your logs - but it might already be solvable by this small change.

Best regards
Mary

@boblinn Looking at the logs I see couple of peculiar settings.

sonar.host.url=https://xxxxxxxxxxxx.com
sonar.scanner.scanAll=false
sonar.cs.vscoverage.reportsPaths=./coverage.xml
sonar.cs.nunit.reportsPaths=./TestResults/TestResults.xml
sonar.exclusions=**/*.settings.json, **/MoqResponse.cs, **/LogicApps.Test/**
sonar.cs.vscoveragexml.reportsPaths=./coverage.xml
sonar.cs.opencover.reportsPaths=coverage.opencover.xml
sonar.cs.vstest.reportsPaths=TestResult.trx
sonar.visualstudio.enable=false

These settings do not match the begin step you posted above.
It looks like both sonar.cs.vscoverage.reportsPaths and sonar.cs.vscoveragexml.reportsPaths are set at the same time.

Same for the test report paths looks likesonar.cs.vstest.reportsPaths=TestResult.trx is set, when you actually need sonar.cs.nunit.reportsPaths.

are these logs maybe not the latest ones?
Also, is it possible that you are inheriting SonarQube server project settings or global settings? Which should not happen, but worth checking (you can check from the SQ Server UI - see docs with more info).

Thank you for those insights.
I updated the sonar.scanner.properties and command line parameters to match as reported, re-ran the begin, build, test and end tasks and the results are the same: the test case metrics are still not shown and under the ‘Measures/Coverage…’ sections of the Sonar report it shows 1 test case even though there are over 380.
I’ve attached the latest ‘begin’ and ‘end’ logs.
Here’s the commands I used:

Begin:

dotnet sonarscanner begin /k:"SEAM" /d:sonar.host.url="https://xxxx.com"  /d:sonar.login="fkdsfls;fidfj" /d:sonar.scanner.scanAll=false /d:sonar.verbose=true /d:sonar.scanner.truststorePassword=sonarlint /d:sonar.scanner.truststorePath=C:\Users\boblinn\.sonarlint\ssl\truststore.p12 /d:sonar.cs.vscoveragexml.reportsPaths=./coverage.xml /d:sonar.cs.nunit.reportsPaths=./TestResults/TestResults.xml > sonarDebugBegin.txt

End:

dotnet sonarscanner end /d:sonar.login="fkdsfls;fidfj" /d:sonar.scanner.truststorePassword=sonarlint > sonarDebugEnd.txt

Here’s my sonar.scanner.properties:

sonar.token=fkdsfls;fidfj

sonar.scanner.scanAll=true
sonar.web.https.truststoreType=PKCS12
sonar.scanner.truststorePath=C:/Users/boblinn/.sonarlint/ssl/truststore.p12
sonar.scanner.truststorePassword=sonarlint
sonar.exclusions=**/Sanmar.Azure.Database.DYNINT*.*, **/LogicApps.Test/**
sonar.cs.vscoveragexml.reportsPaths=./coverage.xml
sonar.cs.nunit.reportsPaths=./TestResults/TestResults.xml

Here’s the log files:
sonarDebugBegin.txt (8.8 KB)
sonarDebugEnd.txt (2.0 MB)

There’s still something I’m missing.
Any help is appreciated.
Thanks
Bob

Hello @boblinn,
So indeed I think I ended up in the same spot as Denis.
Most of the tests cannot be mapped back to the source.

Could you please tell me how you exclude tests projects?
I see this as you pasted above - so you should be defining these exclusions somewhere.

sonar.exclusions=**/Sanmar.Azure.Database.DYNINT*.*, **/LogicApps.Test/**

thanks

I exclude the test projects using the Sonar properties in the test project csproj.
The test project csproj file has these properties:

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
    <SonarQubeTestProject>True</SonarQubeTestProject>
    <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

Note the project is defined as a test project by setting the SonarQubeTestProject property to true.

Hello again!

the property <SonarQubeTestProject>True</SonarQubeTestProject> in the test project is not enough to generate
sonar.exclusions=**/Sanmar.Azure.Database.DYNINT*.*, **/LogicApps.Test/**.

There should be exclusions also somewhere else set - in theory in the server directly in the project settings or the server settings. Could you please check?
What I’m suspicious about is that there might be multiple other analysis settings set that are inducing this behavior.

Another peculiar thing I noticed is that although you set /d:sonar.scanner.scanAll=false
in the sonar.scanner.properties it’s true, which should not happen. Where are these properties copied from? The CLI parameters override any other analysis parameter set anywhere - which happens as I see in the end step.

Thanks again!

The server does not have any exclusions neither in the project nor in the server settings.
Setting the scanAll property to the same value, true or false, did not resolve the issue of not being able to find the sources.
Your help, as always, is appreciated.

Thanks
Bob

Hey @boblinn thanks for your answer.

The server does not have any exclusions neither in the project nor in the server settings.

thanks a lot for checking this out.

Would it be possible for you to create a reproducer project?
From my side I created a toy project similar to what you described, using the same commands for analysis and I don’t have an issue with the tests - so maybe if you could create a repro we can go a bit deeper in the problem.

Thanks a lot in advance!