I have the following (example) component that I am attempting to mock as part of an integration test.
interface MyComponentProps { acceptableRoles: string[]}
export default class MyComponent extends Component<React.PropsWithChildren<MyComponentProps>> {
public canRender(currentRoles: string[]): boolean {
// determine if any current roles intersect with acceptableRoles
}
public render() {
return (
<MyContext.Consumer>
{(roles) => this.canRender(roles)
? this.props.children
: null
}
</MyContext.Consumer>
}
}
I want to mock the output of render()
to remove the <MyContext.Consumer>
wrapper and passing in the roles
into my mock.
When used in the component I'm testing, the above component is simply
<MyComponent acceptableRoles={"role1", "role2"}>
<div>Show this if allowed</div>
</MyComponent>
I have tried a number of things and this feels like the closest I can get; however, I receive an error saying that origin.canRender is not a function
. When I check it, it is undefined.
const myTestRoles = ["role3", "role4"]; //changes within different tests
jest.mock("./MyComponent", () => (props: any) => {
const origin = jest.requireActual<MyComponent>("./MyComponent");
// console.info("Origin: " + origin.canRender); //Outputs as "Origin: undefined"
return <div>{
origin.canRender(_user)
? props.children
: null}
</div>;
});
Everything "works" until I add a call to the `origin.canRender' which provides the "not a function" error.
I know strict unit testing I would just skip this call with an explicit mock but this got me really wondering if this can be done.
Is it possible to call such an instance method from a mocked method?
Since you are using export default MyComponent
, the jest.requireActual('./MyComponent')
statement will return { default: [class MyComponent extends Component] }
.
You can access the public method of a JavaScript class by its .prototype
property.
e.g.
MyComponent.tsx
:
import React, { Component } from 'react';
const MyContext = React.createContext<string[]>([]);
interface MyComponentProps {
acceptableRoles: string[];
}
export default class MyComponent extends Component<React.PropsWithChildren<MyComponentProps>> {
public canRender(currentRoles: string[]): boolean {
return true;
}
public render() {
return <MyContext.Consumer>{(roles) => (this.canRender(roles) ? this.props.children : null)}</MyContext.Consumer>;
}
}
index.test.tsx
:
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
jest.mock('./MyComponent', () => (props: any) => {
const origin = jest.requireActual('./MyComponent');
console.log("🚀 ~ jest.mock ~ origin:", origin)
console.info('Origin: ', origin.default.prototype.canRender);
return <div>{origin.default.prototype.canRender(['role3']) ? props.children : null}</div>;
});
describe('78269182', () => {
test('should pass', () => {
render(
<MyComponent acceptableRoles={['role1', 'role2']}>
<div>Show this if allowed</div>
</MyComponent>,
);
screen.debug();
});
});
Test result:
console.log
🚀 ~ jest.mock ~ origin: { default: [class MyComponent extends Component] }
at log (stackoverflow/78269182/index.test.tsx:7:11)
console.info
Origin: [Function: canRender]
at info (stackoverflow/78269182/index.test.tsx:8:11)
console.log
<body>
<div>
<div>
<div>
Show this if allowed
</div>
</div>
</div>
</body>
at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:87:13)
PASS stackoverflow/78269182/index.test.tsx
78269182
√ should pass (40 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.086 s
Ran all test suites related to changed files.
package versions:
"jest": "^29.7.0"