@@ -49,6 +49,7 @@ import org.mockito.kotlin.doReturn
4949import org.mockito.kotlin.eq
5050import org.mockito.kotlin.inOrder
5151import org.mockito.kotlin.mock
52+ import org.mockito.kotlin.times
5253import org.mockito.kotlin.verify
5354import org.mockito.kotlin.verifyNoInteractions
5455import org.mockito.kotlin.whenever
@@ -439,6 +440,48 @@ internal class NavigationViewTrackingStrategyTest {
439440 verifyNoInteractions(rumMonitor.mockInstance)
440441 }
441442
443+ // region Reproduce RUMS-5363 - duplicate listener registration causes duplicate startView calls
444+
445+ @Test
446+ fun `M register listener only once W startTracking called twice {RUMS-5363}` () {
447+ // Given - activity has started so startTracking can proceed
448+ testedStrategy.register(rumMonitor.mockSdkCore, mockActivity)
449+ testedStrategy.onActivityStarted(mockActivity)
450+
451+ // When - startTracking is called a second time (e.g. dynamic nav setup)
452+ testedStrategy.startTracking()
453+
454+ // Then - addOnDestinationChangedListener must be called exactly once.
455+ // On pre-fix code this fails: the listener is added twice because startTracking()
456+ // has no idempotency guard, so this assertion counts times(2) but we assert times(1).
457+ verify(mockNavController, times(1 )).addOnDestinationChangedListener(testedStrategy)
458+ }
459+
460+ @Test
461+ fun `M call startView once W onDestinationChanged after double startTracking {RUMS-5363}` () {
462+ // Given - register the listener twice by calling startTracking twice
463+ testedStrategy.register(rumMonitor.mockSdkCore, mockActivity)
464+ testedStrategy.onActivityStarted(mockActivity)
465+ // second call re-registers the same listener instance on the NavController
466+ testedStrategy.startTracking()
467+ whenever(mockPredicate.accept(mockNavDestination)) doReturn true
468+
469+ // When - NavController fires the destination-changed event; because the listener was
470+ // registered twice, NavController iterates its list and calls onDestinationChanged twice.
471+ // We simulate that double-dispatch here to prove the downstream effect.
472+ testedStrategy.onDestinationChanged(mockNavController, mockNavDestination, null )
473+ testedStrategy.onDestinationChanged(mockNavController, mockNavDestination, null )
474+
475+ // Then - startView must be called exactly once for the destination.
476+ // On pre-fix code this fails: the double dispatch (caused by double registration) produces
477+ // two startView calls for the same destination, resulting in duplicate resource events.
478+ verify(rumMonitor.mockInstance, times(1 )).startView(
479+ mockNavDestination,
480+ fakeDestinationName,
481+ mapOf (ViewScopeInstrumentationType .FRAGMENT .key.toString() to ViewScopeInstrumentationType .FRAGMENT )
482+ )
483+ }
484+
442485 // endregion
443486
444487 // FragmentLifecycleCallbacks
0 commit comments