Code Coverage Merge Discrepancy Between Unit and Integration Tests

Code Coverage Merge Discrepancy Between Unit and Integration Tests

Must-share information:

  • SonarQube Version: Developer Edition 10.3
  • Deployment: Helm
  • Project Setup:
    • Repository: React with TypeScript
    • Unit tests: Vitest
    • Integration tests: Playwright
  • Goal: Combine unit test and integration test coverage to display overall coverage in SonarQube
  • Configuration: sonar.javascript.lcov.reportPaths=coverage/unit-tests/lcov.info,coverage/integration-tests/lcov.info
  • Issue: SonarQube successfully merges the coverage reports, but we’re observing discrepancies in specific files, particularly with conditional statement coverage.

Problem Description:

We have a file x.ts that is fully covered by integration tests, and we have not written unit test cases for it. However, objects in x.ts get invoked while running the unit tests.

Our hypothesis is that when unit test and integration test coverage reports are merged, although the code lines are covered as part of integration tests, the conditional statements (if conditions) are being marked as “partially covered.” This is leading to an overall drop in coverage percentage.

What we’ve tried so far:

Approach 1: Excluding files from unit tests

  • Action: Added x.ts to the exclude list of unit tests
  • Result: Achieved 100% coverage for x.ts
  • Issue: This approach excludes rather than merges coverage, which doesn’t align with our goal

Approach 2: Manual LCOV report merging

  • Action: Used LCOV utility to explicitly merge reports and configured sonar.javascript.lcov.reportPaths=merged-report-lcov.info
  • Result:
    • :white_check_mark: Works in Community Edition 21.7 (latest)
    • :cross_mark: Doesn’t work in Developer Edition 10.3

Questions:

  1. Is there a recommended approach for merging unit and integration test coverage in SonarQube Developer Edition 10.3?
  2. Are we following the correct practice by trying to combine different types of test coverage?
  3. What could be causing the discrepancy in conditional statement coverage when files are invoked but not marked as covered in unit tests?

Any guidance would be greatly appreciated.

Hi,

Welcome to the community!

Your version is past EOL. You should upgrade to either the latest version or the current LTA (long-term active version) at your earliest convenience. Your upgrade path is:

10.3 → 2025.1.3 → 2025.3.1 (last step optional)

You may find these resources helpful:

If you have questions about upgrading, feel free to open a new thread for that here.

If your error persists after upgrade, please come back to us.

 
Thx,
Ann

Subject: Branch Coverage Discrepancy When Merging Unit Test and Integration Test Reports

Hello SonarQube Community,

We’re experiencing an issue with branch coverage when merging coverage reports from unit tests and integration tests in Sonarqube:25.7.0.110598-community.

Current Observation

Coverage Results:

  • Line coverage: 100% :white_check_mark:
  • Branch coverage: Drops significantly after merging two reports :cross_mark:

Individual Report Behavior:

  • When pushing only the integration test report: Both line coverage and branch coverage show 100%
  • When merging unit test + integration test reports: Line coverage remains 100%, but branch coverage decreases

Technical Setup

Testing Framework:

  • Integration tests: Playwright with Istanbul for coverage
  • Unit tests: Vitest with Istanbul for coverage

Expected Behavior (Union Coverage)

We want to achieve union coverage where:

  1. Lines covered in unit tests but not integration tests → should show as covered in merged report
  2. Lines covered in integration tests but not unit tests → should show as covered in merged report
  3. Lines covered by both → should show as fully covered
  4. Example: If lines 1-10 are covered by unit tests and lines 11-20 by integration tests, the merged report should show lines 1-20 as covered

Current Status:

  • :white_check_mark: Line coverage merging works correctly
  • :cross_mark: Branch coverage merging fails

Question

Why does branch coverage drop when merging reports, while line coverage merges correctly?

Is there a configuration or approach that ensures proper union of branch coverage across multiple test reports?

I have pasted LCOV files from both unit tests and integration tests. let me know if you need complete file (just pasted the branch data)

Any insights or suggestions would be greatly appreciated!

unit test report lcov

LF:73
LH:9
BRDA:364,0,0,0
BRDA:364,0,1,0
BRDA:369,1,0,0
BRDA:369,1,1,0
BRDA:371,2,0,0
BRDA:371,2,1,0
BRDA:373,3,0,0
BRDA:373,3,1,0
BRDA:376,4,0,0
BRDA:376,4,1,0
BRDA:377,5,0,0
BRDA:377,5,1,0
BRDA:385,6,0,0
BRDA:385,6,1,0
BRDA:385,6,2,0
BRDA:389,7,0,0
BRDA:389,7,1,0
BRDA:389,7,2,0
BRDA:438,8,0,0
BRDA:438,8,1,0
BRDA:438,9,0,0
BRDA:438,9,1,0
BRDA:463,10,0,0
BRDA:463,10,1,0
BRDA:464,11,0,0
BRDA:464,11,1,0
BRDA:464,11,2,0
BRDA:465,12,0,0
BRDA:465,12,1,0
BRDA:466,13,0,0
BRDA:466,13,1,0
BRDA:466,13,2,0
BRDA:471,14,0,0
BRDA:471,14,1,0
BRDA:474,15,0,0
BRDA:474,15,1,0
BRDA:475,16,0,0
BRDA:475,16,1,0
BRDA:476,17,0,0
BRDA:476,17,1,0
BRDA:499,18,0,0
BRDA:499,18,1,0
BRDA:501,19,0,0
BRDA:501,19,1,0
BRDA:504,20,0,0
BRDA:504,20,1,0
BRDA:506,21,0,0
BRDA:506,21,1,0
BRDA:510,22,0,0
BRDA:510,22,1,0
BRDA:510,23,0,0
BRDA:510,23,1,0
BRDA:514,24,0,0
BRDA:514,24,1,0
BRDA:529,25,0,0
BRDA:529,25,1,0
BRF:56
BRH:0
end_of_record

integration report , where condition coverage 100%

LF:73
LH:73
BRDA:364,0,0,6
BRDA:369,1,0,113
BRDA:369,1,1,3
BRDA:371,2,0,107
BRDA:371,2,1,3
BRDA:373,3,0,107
BRDA:373,3,1,3
BRDA:376,4,0,113
BRDA:376,4,1,3
BRDA:377,5,0,113
BRDA:377,5,1,3
BRDA:385,6,0,107
BRDA:385,6,1,94
BRDA:385,6,2,88
BRDA:389,7,0,107
BRDA:389,7,1,10
BRDA:389,7,2,10
BRDA:438,8,0,3
BRDA:438,8,1,0
BRDA:438,9,0,3
BRDA:438,9,1,0
BRDA:463,10,0,482
BRDA:463,10,1,88
BRDA:464,11,0,482
BRDA:464,11,1,394
BRDA:464,11,2,312
BRDA:465,12,0,482
BRDA:465,12,1,14
BRDA:466,13,0,482
BRDA:466,13,1,404
BRDA:466,13,2,394
BRDA:471,14,0,406
BRDA:474,15,0,14
BRDA:474,15,1,9
BRDA:475,16,0,14
BRDA:475,16,1,12
BRDA:476,17,0,14
BRDA:476,17,1,9
BRDA:499,18,0,14
BRDA:499,18,1,394
BRDA:501,19,0,394
BRDA:501,19,1,291
BRDA:504,20,0,307
BRDA:506,21,0,13
BRDA:510,22,0,2
BRDA:510,23,0,294
BRDA:510,23,1,9
BRDA:514,24,0,21
BRDA:514,24,1,271
BRDA:529,25,0,147
BRDA:529,25,1,140
BRF:51
BRH:49
end_of_record

Hi,

It’s not clear to me why you would have updated from 10.3 to 10.7. Even on 10.7, we can’t help you. You need to update to a current version of SonarQube.

 
Ann

apologies for bad typo.

I updated above msg, it’s sonarqube:25.7.0.110598-community version.

Hi,

Thanks for the correction. :sweat_smile:

To be explicit, I guess we’re talking about JavaScript?

Could I have a screenshot of a few lines that show up as uncovered once the reports are imported, but covered in your 100% integration test report?

Also, how are you performing the merge?

 
Ann

Hi Ann,

Yes, the repository is a React project with TypeScript.

We’re passing both reports via sonar-project.properties using the following configuration:

sonar.javascript.lcov.reportPaths=coverage/unit-tests/lcov.info,coverage/integration-tests/lcov.info

Screenshots:

Let me know if you’d like any additional details.

Thank you!

Hi,

Thanks for the screenshots. I’m going to flag this for the language experts.

In the meantime, would you be comfortable posting your two coverage reports? Or at least the fragments of those reports that correspond to your screenshots?

 
Thx,
Ann

Hi Ann,

Thank you for the response,

I’ve shared coverage reports , in my 2nd message in message thread. HERE

Sharing it here again full lcov report of the screenshot file.

Integration test report

SF: SomeTypeScriptFile.tsx
FN:28,(anonymous_0)
FN:324,(anonymous_1)
FN:336,(anonymous_2)
FN:355,(anonymous_3)
FN:359,(anonymous_4)
FN:362,(anonymous_5)
FN:369,(anonymous_6)
FN:374,(anonymous_7)
FN:384,(anonymous_8)
FN:385,(anonymous_9)
FN:388,(anonymous_10)
FN:389,(anonymous_11)
FN:392,(anonymous_12)
FN:393,(anonymous_13)
FN:420,(anonymous_14)
FN:425,(anonymous_15)
FN:430,(anonymous_16)
FN:433,(anonymous_17)
FN:435,(anonymous_18)
FN:438,(anonymous_19)
FN:451,(anonymous_20)
FN:470,(anonymous_21)
FN:480,(anonymous_22)
FN:497,(anonymous_23)
FN:521,(anonymous_24)
FN:525,(anonymous_25)
FN:525,(anonymous_26)
FNF:27
FNH:27
FNDA:14,(anonymous_0)
FNDA:659,(anonymous_1)
FNDA:14,(anonymous_2)
FNDA:14,(anonymous_3)
FNDA:14,(anonymous_4)
FNDA:119,(anonymous_5)
FNDA:107,(anonymous_6)
FNDA:113,(anonymous_7)
FNDA:107,(anonymous_8)
FNDA:100,(anonymous_9)
FNDA:107,(anonymous_10)
FNDA:10,(anonymous_11)
FNDA:85,(anonymous_12)
FNDA:10,(anonymous_13)
FNDA:14,(anonymous_14)
FNDA:14,(anonymous_15)
FNDA:119,(anonymous_16)
FNDA:3,(anonymous_17)
FNDA:3,(anonymous_18)
FNDA:3,(anonymous_19)
FNDA:482,(anonymous_20)
FNDA:420,(anonymous_21)
FNDA:147,(anonymous_22)
FNDA:408,(anonymous_23)
FNDA:408,(anonymous_24)
FNDA:7,(anonymous_25)
FNDA:7,(anonymous_26)
DA:28,14
DA:29,14
DA:31,14
DA:324,659
DA:327,14
DA:336,14
DA:337,14
DA:338,14
DA:340,14
DA:355,14
DA:356,14
DA:359,14
DA:360,14
DA:362,14
DA:364,119
DA:365,6
DA:369,113
DA:370,107
DA:375,113
DA:383,113
DA:385,107
DA:387,113
DA:389,107
DA:392,113
DA:393,113
DA:394,113
DA:396,113
DA:404,14
DA:407,14
DA:420,14
DA:421,14
DA:422,14
DA:425,14
DA:426,14
DA:428,14
DA:431,119
DA:434,3
DA:435,3
DA:436,3
DA:438,3
DA:444,14
DA:447,14
DA:451,14
DA:463,482
DA:464,482
DA:465,482
DA:466,482
DA:470,14
DA:471,420
DA:472,406
DA:474,14
DA:475,14
DA:476,14
DA:480,14
DA:488,147
DA:489,147
DA:490,147
DA:492,147
DA:493,147
DA:496,147
DA:499,408
DA:503,408
DA:504,408
DA:506,307
DA:507,13
DA:510,294
DA:511,2
DA:514,292
DA:517,292
DA:519,101
DA:521,408
DA:524,147
DA:525,7
LF:73
LH:73
BRDA:364,0,0,6
BRDA:369,1,0,113
BRDA:369,1,1,3
BRDA:371,2,0,107
BRDA:371,2,1,3
BRDA:373,3,0,107
BRDA:373,3,1,3
BRDA:376,4,0,113
BRDA:376,4,1,3
BRDA:377,5,0,113
BRDA:377,5,1,3
BRDA:385,6,0,107
BRDA:385,6,1,94
BRDA:385,6,2,88
BRDA:389,7,0,107
BRDA:389,7,1,10
BRDA:389,7,2,10
BRDA:438,8,0,3
BRDA:438,8,1,0
BRDA:438,9,0,3
BRDA:438,9,1,0
BRDA:463,10,0,482
BRDA:463,10,1,88
BRDA:464,11,0,482
BRDA:464,11,1,394
BRDA:464,11,2,312
BRDA:465,12,0,482
BRDA:465,12,1,14
BRDA:466,13,0,482
BRDA:466,13,1,404
BRDA:466,13,2,394
BRDA:471,14,0,406
BRDA:474,15,0,14
BRDA:474,15,1,9
BRDA:475,16,0,14
BRDA:475,16,1,12
BRDA:476,17,0,14
BRDA:476,17,1,9
BRDA:499,18,0,14
BRDA:499,18,1,394
BRDA:501,19,0,394
BRDA:501,19,1,291
BRDA:504,20,0,307
BRDA:506,21,0,13
BRDA:510,22,0,2
BRDA:510,23,0,294
BRDA:510,23,1,9
BRDA:514,24,0,21
BRDA:514,24,1,271
BRDA:529,25,0,147
BRDA:529,25,1,140
BRF:51
BRH:49
end_of_record

unit test report

SF:SomeTypeScriptFile.tsx
FN:28,(anonymous_0)
FN:324,(anonymous_1)
FN:336,(anonymous_2)
FN:355,(anonymous_3)
FN:359,(anonymous_4)
FN:362,(anonymous_5)
FN:369,(anonymous_6)
FN:374,(anonymous_7)
FN:384,(anonymous_8)
FN:385,(anonymous_9)
FN:388,(anonymous_10)
FN:389,(anonymous_11)
FN:392,(anonymous_12)
FN:393,(anonymous_13)
FN:420,(anonymous_14)
FN:425,(anonymous_15)
FN:430,(anonymous_16)
FN:433,(anonymous_17)
FN:435,(anonymous_18)
FN:438,(anonymous_19)
FN:451,(anonymous_20)
FN:470,(anonymous_21)
FN:480,(anonymous_22)
FN:497,(anonymous_23)
FN:521,(anonymous_24)
FN:525,(anonymous_25)
FN:525,(anonymous_26)
FNF:27
FNH:0
FNDA:0,(anonymous_0)
FNDA:0,(anonymous_1)
FNDA:0,(anonymous_2)
FNDA:0,(anonymous_3)
FNDA:0,(anonymous_4)
FNDA:0,(anonymous_5)
FNDA:0,(anonymous_6)
FNDA:0,(anonymous_7)
FNDA:0,(anonymous_8)
FNDA:0,(anonymous_9)
FNDA:0,(anonymous_10)
FNDA:0,(anonymous_11)
FNDA:0,(anonymous_12)
FNDA:0,(anonymous_13)
FNDA:0,(anonymous_14)
FNDA:0,(anonymous_15)
FNDA:0,(anonymous_16)
FNDA:0,(anonymous_17)
FNDA:0,(anonymous_18)
FNDA:0,(anonymous_19)
FNDA:0,(anonymous_20)
FNDA:0,(anonymous_21)
FNDA:0,(anonymous_22)
FNDA:0,(anonymous_23)
FNDA:0,(anonymous_24)
FNDA:0,(anonymous_25)
FNDA:0,(anonymous_26)
DA:28,1
DA:29,0
DA:31,0
DA:324,1
DA:327,1
DA:336,1
DA:337,0
DA:338,0
DA:340,0
DA:355,1
DA:356,0
DA:359,0
DA:360,0
DA:362,0
DA:364,0
DA:365,0
DA:369,0
DA:370,0
DA:375,0
DA:383,0
DA:385,0
DA:387,0
DA:389,0
DA:392,0
DA:393,0
DA:394,0
DA:396,0
DA:404,0
DA:407,0
DA:420,1
DA:421,0
DA:422,0
DA:425,0
DA:426,0
DA:428,0
DA:431,0
DA:434,0
DA:435,0
DA:436,0
DA:438,0
DA:444,0
DA:447,0
DA:451,1
DA:463,0
DA:464,0
DA:465,0
DA:466,0
DA:470,1
DA:471,0
DA:472,0
DA:474,0
DA:475,0
DA:476,0
DA:480,1
DA:488,0
DA:489,0
DA:490,0
DA:492,0
DA:493,0
DA:496,0
DA:499,0
DA:503,0
DA:504,0
DA:506,0
DA:507,0
DA:510,0
DA:511,0
DA:514,0
DA:517,0
DA:519,0
DA:521,0
DA:524,0
DA:525,0
LF:73
LH:9
BRDA:364,0,0,0
BRDA:364,0,1,0
BRDA:369,1,0,0
BRDA:369,1,1,0
BRDA:371,2,0,0
BRDA:371,2,1,0
BRDA:373,3,0,0
BRDA:373,3,1,0
BRDA:376,4,0,0
BRDA:376,4,1,0
BRDA:377,5,0,0
BRDA:377,5,1,0
BRDA:385,6,0,0
BRDA:385,6,1,0
BRDA:385,6,2,0
BRDA:389,7,0,0
BRDA:389,7,1,0
BRDA:389,7,2,0
BRDA:438,8,0,0
BRDA:438,8,1,0
BRDA:438,9,0,0
BRDA:438,9,1,0
BRDA:463,10,0,0
BRDA:463,10,1,0
BRDA:464,11,0,0
BRDA:464,11,1,0
BRDA:464,11,2,0
BRDA:465,12,0,0
BRDA:465,12,1,0
BRDA:466,13,0,0
BRDA:466,13,1,0
BRDA:466,13,2,0
BRDA:471,14,0,0
BRDA:471,14,1,0
BRDA:474,15,0,0
BRDA:474,15,1,0
BRDA:475,16,0,0
BRDA:475,16,1,0
BRDA:476,17,0,0
BRDA:476,17,1,0
BRDA:499,18,0,0
BRDA:499,18,1,0
BRDA:501,19,0,0
BRDA:501,19,1,0
BRDA:504,20,0,0
BRDA:504,20,1,0
BRDA:506,21,0,0
BRDA:506,21,1,0
BRDA:510,22,0,0
BRDA:510,22,1,0
BRDA:510,23,0,0
BRDA:510,23,1,0
BRDA:514,24,0,0
BRDA:514,24,1,0
BRDA:529,25,0,0
BRDA:529,25,1,0
BRF:56
BRH:0
end_of_record
1 Like

Hello Ann,

Any update on above

Hi,

This is flagged for the language experts. Hopefully they’ll be along soon.

 
Ann

1 Like

Hello @Sheikh_Khurram,

I have not been able to reproduce this. I’m playing with this file

// src/file1.ts
if (x > 0) {
    console.log("positive");
}

If I use this 2 lcov files, both conditions appear as covered.

SF:src/file1.ts
BRDA:1,0,0,3
BRDA:1,0,1,0
end_of_record
SF:src/file1.ts
BRDA:1,0,0,0
BRDA:1,0,1,4
end_of_record

Could you please provide a minimum reproducer like the one I’m using which shows a case where the merge does not happen?

Thanks!