Skip to content

Commit

Permalink
feat(instrumentation-react-native-navigation) adding unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
facostaembrace committed Aug 5, 2024
1 parent ab87452 commit d75cb7f
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
extension: ["ts", "tsx"]
spec: ["test/**/*.test.tsx"]
spec: ["test/**/*.test.tsx", "test/**/*.test.ts"]
require: ["jsdom.register.js", "babel.register.js"]
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ As mentioned before, <NavigationTracker /> relies on `@react-native/navigation`
### Note

`useProvider` hook in this example returns an instance of a configured provided.
It doesn't matter which provider you choose; you just need to pass down one (if needed) with all your configurations. To create that provider, you may want to refer to the official [OpenTelemetry JS documentation](https://github.com/open-telemetry/opentelemetry-js). You can also review our suggested implementation (`./test/hooks/useProvider.ts`), but keep in mind that this is the simplest provider with minimal configurations.
It doesn't matter which provider you choose; you just need to pass down one (if needed) with all your configurations. To create that provider, you may want to refer to the official [OpenTelemetry JS documentation](https://github.com/open-telemetry/opentelemetry-js). You can also review our suggested implementation (`./test/helpers/hooks/useProvider.ts`), but keep in mind that this is the simplest provider with minimal configurations.

## Useful links

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const useNativeNavigationTracker = (
) => {
const navigationElRef = useMemo(() => {
const isMutableRef = ref !== null && typeof ref !== 'function';
return isMutableRef ? ref.current : undefined;
return isMutableRef ? ref?.current : undefined;
}, [ref]);

const { attributes: customAttributes, debug } = config ?? {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const useNavigationTracker = (
) => {
const navigationElRef = useMemo(() => {
const isMutableRef = ref !== null && typeof ref !== 'function';
return isMutableRef ? ref.current : undefined;
return isMutableRef ? ref?.current : undefined;
}, [ref]);

const { attributes: customAttributes, debug } = config ?? {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,24 @@ import { useMemo } from 'react';

const consoleStub = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
log: (_: string) => {},
log: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
warn: (_: string) => {},
warn: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
error: (_: string) => {},
error: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
info: (_: string) => {},
info: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
trace: (_: string) => {},
trace: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
debug: (_: string) => {},
debug: (..._: unknown[]) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
table: () => {},
groupEnd: () => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
group: (_?: string) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
groupCollapsed: (_?: string) => {},
groupEnd: () => {},
};

const useConsole = (debug: boolean) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { render } from '@testing-library/react';
import { ATTRIBUTES } from '../src/utils/spanFactory';
import sinon from 'sinon';
import { NativeNavigationTracker } from '../src';
import useProvider from './hooks/useProvider';
import useProvider from './helpers/hooks/useProvider';
import api from '@opentelemetry/api';
import * as rnn from './helpers/react-native-navigation';
import { NavigationTrackerConfig } from '../src/types/navigation';
Expand Down Expand Up @@ -50,6 +50,7 @@ describe('NativeNavigationTracker.tsx', function () {

let mockConsoleDir: sinon.SinonSpy;
let mockConsoleInfo: sinon.SinonSpy;
let mockConsoleWarn: sinon.SinonSpy;
let mockAddEventListener: sinon.SinonSpy;

let mockGlobalTracer: sinon.SinonSpy;
Expand All @@ -64,6 +65,7 @@ describe('NativeNavigationTracker.tsx', function () {
mockAddEventListener = sandbox.spy(AppState, 'addEventListener');
mockConsoleDir = sandbox.spy(console, 'dir');
mockConsoleInfo = sandbox.spy(console, 'info');
mockConsoleWarn = sandbox.spy(console, 'warn');

mockGlobalTracer = sandbox.spy(api.trace, 'getTracer');
});
Expand All @@ -72,6 +74,44 @@ describe('NativeNavigationTracker.tsx', function () {
sandbox.restore();
});

it('should not start tracking if the `ref` is not found', function () {
const screen = render(
<NativeNavigationTracker ref={{ current: null }} config={{ debug: true }}>
the app goes here
</NativeNavigationTracker>
);

sandbox.assert.calledWithExactly(
mockConsoleWarn,
'Navigation ref is not available. Make sure this is properly configured.'
);

sandbox.assert.match(!!screen.getByText('the app goes here'), true);
});

it('should render the proper warn messages if the `componentName` is not found', function () {
render(
<AppWithProvider shouldPassProvider={false} config={{ debug: true }} />
);

const mockDidAppearListenerCall = mockDidAppearListener.getCall(0).args[0];
mockDidAppearListenerCall({ componentName: null });

sandbox.assert.calledWithExactly(
mockConsoleWarn,
'Navigation component name is not available. Make sure this is properly configured.'
);

const mockDidDisappearListenerCall =
mockDidDisappearListener.getCall(0).args[0];
mockDidDisappearListenerCall({ componentName: null });

sandbox.assert.calledWithExactly(
mockConsoleWarn,
'Navigation component name is not available. Make sure this is properly configured.'
);
});

it('should render a component that implements <NativeNavigationTracker /> without passing a provider', function () {
const screen = render(
<AppWithProvider shouldPassProvider={false} config={{ debug: true }} />
Expand All @@ -90,13 +130,13 @@ describe('NativeNavigationTracker.tsx', function () {

sandbox.assert.calledWith(mockDidAppearListener, sandbox.match.func);

const mockDidAppearListenerCall = mockDidAppearListener.getCall(0).args[0];
const mockDidAppearListenerCall = mockDidAppearListener.getCall(1).args[0];

mockDidAppearListenerCall({ componentName: 'homeView' });
sandbox.assert.calledWith(mockDidDisappearListener, sandbox.match.func);

const mockDidDisappearListenerCall =
mockDidDisappearListener.getCall(0).args[0];
mockDidDisappearListener.getCall(1).args[0];

mockDidDisappearListenerCall({ componentName: 'homeView' });

Expand Down Expand Up @@ -145,13 +185,13 @@ describe('NativeNavigationTracker.tsx', function () {
sandbox.assert.notCalled(mockGlobalTracer);

sandbox.assert.calledWith(mockDidAppearListener, sandbox.match.func);
const mockDidAppearListenerCall = mockDidAppearListener.getCall(1).args[0];
const mockDidAppearListenerCall = mockDidAppearListener.getCall(2).args[0];

mockDidAppearListenerCall({ componentName: 'homeView' });

sandbox.assert.calledWith(mockDidDisappearListener, sandbox.match.func);
const mockDidDisappearListenerCall =
mockDidDisappearListener.getCall(1).args[0];
mockDidDisappearListener.getCall(2).args[0];

mockDidDisappearListenerCall({ componentName: 'homeView' });

Expand Down Expand Up @@ -196,9 +236,9 @@ describe('NativeNavigationTracker.tsx', function () {
it('should start and end spans when the app changes the status between foreground/background', function () {
const screen = render(<AppWithProvider shouldPassProvider={true} />);

const mockDidAppearListenerCall = mockDidAppearListener.getCall(2).args[0];
const mockDidAppearListenerCall = mockDidAppearListener.getCall(3).args[0];
const mockDidDisappearListenerCall =
mockDidDisappearListener.getCall(2).args[0];
mockDidDisappearListener.getCall(3).args[0];

const handleAppStateChange = mockAddEventListener.getCall(0).args[1];

Expand Down Expand Up @@ -288,9 +328,9 @@ describe('NativeNavigationTracker.tsx', function () {
/>
);

const mockDidAppearListenerCall = mockDidAppearListener.getCall(3).args[0];
const mockDidAppearListenerCall = mockDidAppearListener.getCall(4).args[0];
const mockDidDisappearListenerCall =
mockDidDisappearListener.getCall(3).args[0];
mockDidDisappearListener.getCall(4).args[0];

mockDidAppearListenerCall({ componentName: 'homeCustomAttributesView' });
mockDidDisappearListenerCall({ componentName: 'homeCustomAttributesView' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { FC, ForwardedRef } from 'react';
import React, { render } from '@testing-library/react';

import useProvider from './hooks/useProvider';
import useProvider from './helpers/hooks/useProvider';

import { NavRef } from '../src/hooks/useNavigationTracker';
import { ATTRIBUTES } from '../src/utils/spanFactory';
Expand Down Expand Up @@ -56,6 +56,7 @@ describe('NavigationTracker.tsx', function () {
let mockAddEventListener: sinon.SinonSpy;
let mockConsoleDir: sinon.SinonSpy;
let mockConsoleInfo: sinon.SinonSpy;
let mockConsoleWarn: sinon.SinonSpy;

let mockGlobalTracer: sinon.SinonSpy;

Expand All @@ -73,6 +74,7 @@ describe('NavigationTracker.tsx', function () {
mockAddEventListener = sandbox.spy(AppState, 'addEventListener');
mockConsoleDir = sandbox.spy(console, 'dir');
mockConsoleInfo = sandbox.spy(console, 'info');
mockConsoleWarn = sandbox.spy(console, 'warn');

mockGlobalTracer = sandbox.spy(api.trace, 'getTracer');
});
Expand All @@ -81,6 +83,37 @@ describe('NavigationTracker.tsx', function () {
sandbox.restore();
});

it('should not start tracking if the `ref` is not found', function () {
const screen = render(
<NavigationTracker ref={{ current: null }} config={{ debug: true }}>
the app goes here
</NavigationTracker>
);

sandbox.assert.calledWithExactly(
mockConsoleWarn,
'Navigation ref is not available. Make sure this is properly configured.'
);

sandbox.assert.match(!!screen.getByText('the app goes here'), true);
});

it('should render the proper warn messages if the `routeName` is not found', function () {
render(
<AppWithProvider shouldPassProvider={false} config={{ debug: true }} />
);

const mockNavigationListenerCall = mockAddListener.getCall(0).args[1];

mockGetCurrentRoute.returns({ name: null });
mockNavigationListenerCall();

sandbox.assert.calledWithExactly(
mockConsoleWarn,
'Navigation route name is not available. Make sure this is properly configured.'
);
});

it('should render a component that implements <NavigationTracker /> without passing a provider', function () {
const screen = render(
<AppWithProvider shouldPassProvider={false} config={{ debug: true }} />
Expand All @@ -98,7 +131,7 @@ describe('NavigationTracker.tsx', function () {
);

sandbox.assert.calledWith(mockAddListener, 'state', sandbox.match.func);
const mockNavigationListenerCall = mockAddListener.getCall(0).args[1];
const mockNavigationListenerCall = mockAddListener.getCall(1).args[1];

mockGetCurrentRoute.returns({ name: 'homeView' });
mockNavigationListenerCall();
Expand Down Expand Up @@ -156,7 +189,7 @@ describe('NavigationTracker.tsx', function () {
sandbox.assert.notCalled(mockGlobalTracer);

sandbox.assert.calledWith(mockAddListener, 'state', sandbox.match.func);
const mockNavigationListenerCall = mockAddListener.getCall(1).args[1];
const mockNavigationListenerCall = mockAddListener.getCall(2).args[1];

mockGetCurrentRoute.returns({ name: 'homeView' });
mockNavigationListenerCall();
Expand Down Expand Up @@ -211,7 +244,7 @@ describe('NavigationTracker.tsx', function () {
// app launches
const screen = render(<AppWithProvider shouldPassProvider={true} />);

const mockNavigationListenerCall = mockAddListener.getCall(2).args[1];
const mockNavigationListenerCall = mockAddListener.getCall(3).args[1];
const handleAppStateChange = mockAddEventListener.getCall(0).args[1];

// app launches, navigation listener is called
Expand Down Expand Up @@ -303,7 +336,7 @@ describe('NavigationTracker.tsx', function () {
/>
);

const mockNavigationListenerCall = mockAddListener.getCall(3).args[1];
const mockNavigationListenerCall = mockAddListener.getCall(4).args[1];

mockGetCurrentRoute.returns({ name: 'homeCustomAttributes' });
mockNavigationListenerCall();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { renderHook } from '@testing-library/react';
import useConsole from '../src/utils/hooks/useConsole';
import { beforeEach, afterEach } from 'mocha';
import sinon from 'sinon';

describe('useConsole.ts', function () {
const sandbox = sinon.createSandbox();

let mockConsoleInfo: sinon.SinonSpy;
let mockConsoleWarn: sinon.SinonSpy;

beforeEach(function () {
mockConsoleInfo = sandbox.spy(console, 'info');
mockConsoleWarn = sandbox.spy(console, 'warn');
});

afterEach(function () {
sandbox.restore();
});

it('should not to print messages', () => {
const { result } = renderHook(() => useConsole(false));
const customConsole = result.current;

customConsole.info('Info log.');
customConsole.warn('Warn log.');

sandbox.assert.notCalled(mockConsoleInfo);
sandbox.assert.notCalled(mockConsoleWarn);
});

it('should print messages', () => {
const { result } = renderHook(() => useConsole(true));
const customConsole = result.current;

customConsole.info('Info log.');
customConsole.warn('Warn log.');

sandbox.assert.calledOnceWithExactly(mockConsoleInfo, 'Info log.');
sandbox.assert.calledOnceWithExactly(mockConsoleWarn, 'Warn log.');
});

it('should work as expected when the argument is updated', () => {
const { result: customConsoleNotPrinting } = renderHook(() =>
useConsole(false)
);

customConsoleNotPrinting.current.info('Info log.');
sandbox.assert.notCalled(mockConsoleInfo);

const { result: customConsolePrinting } = renderHook(() =>
useConsole(true)
);

customConsolePrinting.current.info('Info log printed.');
sandbox.assert.calledOnceWithExactly(mockConsoleInfo, 'Info log printed.');
});
});

0 comments on commit d75cb7f

Please sign in to comment.