I encountered a problem during the TDD process and the state of react hooks did not change as expected
// Header.tsx
import React, { useState, ChangeEvent, KeyboardEvent } from 'react';
interface Props {
addUndoItem: (item: string) => void;
}
function Header(props: Props) {
const [value, setValue] = useState('');
const handleChange = (e: ChangeEvent<{ value: string }>) => {
setValue(e.target.value);
};
const handleKeyUp = (e: KeyboardEvent) => {
if (value && e.keyCode === 13) {
props.addUndoItem(value);
}
};
return (
<div>
<input
onChange={handleChange}
onKeyUp={handleKeyUp}
value={value}
data-test="input"
/>
</div>
);
}
// tests/Header.tsx
it('the fn may invoked when press enter', () => {
const userInput = 'test';
const fn = jest.fn();
const wrapper = shallow(<Header addUndoItem={fn} />);
const inputEle = wrapper.find('[data-test="input"]');
inputEle.simulate('change', {
target: {
value: userInput
}
});
inputEle.simulate('keyUp', {
keyCode: 13
});
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(userInput);
});
when exec simulate change, the value in Header hooks is still '' it should be test, it run successful in browser
the error is
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
48 | keyCode: 13
49 | });
> 50 | expect(fn).toHaveBeenCalled();
| ^
51 | expect(fn).toHaveBeenCalledWith(userInput);
52 | });
53 |
You need to use mount instead of shallow, otherwise your input component won't be properly rendered and therefore won't be usable during the test.
Related
I have this react component which uses react-signature-canvas.
I want to add some tests but I get this error and Im ot sure how to fix it.
TypeError: Cannot read property 'off' of null
29 | describe("HandSignature", () => {
30 | it("checks for classname", () => {
> 31 | render(<Wrapper />);
| ^
32 | const btn = screen.getByTestId("save");
33 | expect(btn).toHaveClass("saveBtn");
34 | });
at t.r.off (node_modules/react-signature-canvas/build/index.js:1:3235)
The component I want to test is this:
import React, { useRef } from "react";
import SignatureCanvas from "react-signature-canvas";
...
const canvasProps = {
role: "img",
"aria-label": i18n.t("changeService:signature.signature"),
className: Styles.signature,
};
type Props = {
submit: Function,
refDom: Object,
signature: Object,
setSignature: Function,
};
const HandSignature = ({ submit, refDom, signature, setSignature }: Props) => {
const { t } = useTranslation("common");
const _resizedSigCanvas: Object = useRef({});
return (
<>
<div className={Styles.signatureCanvasWrapper}>
<div className={Styles.aspectRatio8x2}>
<div className={Styles.aspectRatioInnerWrapper}>
<div id="resizedCanvas" ref={_resizedSigCanvas}></div>
<SignatureCanvas ref={refDom} canvasProps={canvasProps} />
</div>
</div>
</div>
</>
);
};
export default HandSignature;
I read in the internet that I need to add the package jest-canvas-mock in order to test the canvas, So i added it to my package.json, I also added this to my package.json:
"jest": {
"setupFiles": ["jest-canvas-mock"]
}
But when I run it I get this error: Cannot read property 'off' of null
This is my test:
import "#testing-library/jest-dom/extend-expect";
import "jest-canvas-mock";
import { Form } from "react-final-form";
import React from "react";
import { screen, render } from "#testing-library/react";
import HandSignature from "./HandSignature";
// const state = {};
beforeEach(jest.resetAllMocks);
const Wrapper = () => {
const refDom = React.useRef({});
const props = {
submit: jest.fn(),
refDom,
signature: null,
setSignature: jest.fn(),
};
return (
<Form onSubmit={() => {}}>
{() => {
return <HandSignature {...props} />;
}}
</Form>
);
};
describe("HandSignature", () => {
it("test classname", () => {
render(<Wrapper />);
const btn = screen.getByTestId("save");
expect(btn).toHaveClass("saveBtn");
});
});
Any ideas on how to fix this?
I am new with react native and trying to connect database with my programs. I am getting this error and I don't know what to try to fix it...
TypeError: The "original" argument must be of type Function
Profil
src/jsoni/Profil.js:4
1 | import React from 'react';
2 | import { StyleSheet, SafeAreaView,TouchableOpacity,Text} from 'react-native';
3 |
> 4 | export default function Profil({navigation}) {
5 |
6 |
7 | const { Client } = require("cassandra-driver");
...and this one...
Unhandled Rejection (TypeError): Client is not a constructor
registerRootComponent
src/launch/registerRootComponent.web.tsx:14
11 | const RootComponent: React.FC<P> = props => <App {...props} />;
12 | AppRegistry.registerComponent('main', () => RootComponent);
13 | const rootTag = document.getElementById('root') ?? document.getElementById('main');
> 14 | AppRegistry.runApplication('main', { rootTag });
15 | }
16 |
I am using DataStax Astra for database and here is what I am trying to do https://docs.datastax.com/en/astra/docs/connecting-to-your-database-with-the-datastax-nodejs-driver.html
This is my code...
import React from 'react';
import { StyleSheet, SafeAreaView,TouchableOpacity,Text} from 'react-native';
export default function Profil({navigation}) {
const { Client } = require("cassandra-driver");
async function run() {
const client = new Client({
cloud: {
secureConnectBundle: "../../secure-connect-test.zip",
},
credentials: {
username: "myusername",
password: "mypassword",
},
});
await client.connect();
// Execute a query
const rs = await client.execute("SELECT firstname FROM keyspace.table");
console.log(rs.rows);
await client.shutdown();
}
// Run the async function
run();
const press = () => {
navigation.navigate('Home');
}
return (
<TouchableOpacity onPress={press}>
<SafeAreaView>
<SafeAreaView style={styles.border}/>
<Text>Text here</Text>
</SafeAreaView>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
border: {
height: 50,
width:100,
backgroundColor:'#ff69ff',
},
});
Instead of importing the Client inside the component with require try a regular import at the top of the file:
// Remove this line
const { Client } = require("cassandra-driver");
// and import at the top of the file =>
import React from 'react';
import { ... } from 'react-native';
import { Client } from 'cassandra-driver';
Edit: Looks like the Cassandra Driver is a tool to be used in the backend with node, so probably this plugin will not work in React-Native. So you'll have to set up a backend in node and then fetch the results in ReactNaive from there.
This test:
import { render } from '#testing-library/svelte';
import { setRoutes } from '../router';
import SLink from '../router/components/SLink.svelte';
import routes from '../routes';
beforeAll(() => setRoutes(routes));
test('Instantiates components', () => {
expect(() => render(SLink, { props: { name: 'About' } })).not.toThrow();
});
Produces this error:
Error name: "TypeError"
Error message: "Cannot read property 'forEach' of undefined"
137 |
138 | const matchRoute = passedRoutes => {
> 139 | passedRoutes.forEach(compare => {
| ^
140 | if (matchedRoute) return;
141 |
142 | const { regex, fullRegex } = compare as FormattedRoute;
Which comes from this function:
const compareRoutes = (routes, route) => {
let matchedRoute;
const matchRoute = passedRoutes => {
passedRoutes.forEach(compare => {
// ...
});
};
matchRoute(routes);
return matchedRoute;
};
Which is called by the component I'm trying to render in the test:
<script>
import { routes } from '../logic';
import { compareRoutes } from '../static';
export let name;
// Error stems from 'routes' being undefined here
const route = compareRoutes(routes, { name });
</script>
This line:
beforeAll(() => setRoutes(routes));
Sets the routes imported by SLink which are then passed to compareRoutes, so they shouldn't be undefined.
I've used the same line for other functions and the tests run as expected.
Can #testing-library/svelte not resolve imports? Or, is there another reason for this?
setRoutes:
let routes;
const setRoutes = (userRoutes) => {
// ...
routes = userRoutes;
};
export { routes };
I guess this is rendered in JSDom, and I suspect it might not support live bindings like what you're doing with your export { routes }.
Try exporting a function instead, to confirm:
export const getRoutes = () => routes
Also, this is off topic, but it really feels like your compareRoutes function could be simplified as a filter (or map, or reduce) operation. Something like that:
const compareRoutes = (routes, route) => routes.filter(
x => route.path.startsWith(x.path)
)
There is code in our codebase like below:
#Validate(Param1)
async post(request, responseHandler) {
// some code
}
I Am trying to test the post function. But want to avoid evaluating the #Validate function. The Validate is a function in another module.
// validator.ts
export const Validate = () => {
// some code
}
How to? .
You could use jest.mock(moduleName, factory, options) create the mocked Validate decorator instead of using the real Validate decorator which may have a lot of validation rules.
E.g.
index.ts:
import { Validate } from './validator';
export class Controller {
#Validate('params')
async post(request, responseHandler) {
console.log('real post implementation');
}
}
validator.ts:
export const Validate = (params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('real validator decorator implementation');
// lots of validation
const rval = oFunc.apply(this, args);
return rval;
};
};
};
index.test.ts:
import { Validate } from './validator';
import { mocked } from 'ts-jest/utils';
jest.mock('./validator');
describe('63531414', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mocked(Validate).mockImplementationOnce((params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('mocked validator decorator implementation');
const rval = oFunc.apply(this, args);
return rval;
};
};
});
const { Controller } = require('./');
const logSpy = jest.spyOn(console, 'log');
const ctrl = new Controller();
await ctrl.post({}, () => {});
expect(Validate).toBeCalledWith('params');
expect(logSpy).toBeCalledWith('real post implementation');
});
});
unit test result with coverage report:
PASS src/stackoverflow/63531414/index.test.ts (12.634s)
63531414
✓ should pass (154ms)
console.log node_modules/jest-mock/build/index.js:860
mocked validator decorator implementation
console.log node_modules/jest-mock/build/index.js:860
real post implementation
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 45.45 | 100 | 25 | 45.45 | |
index.ts | 100 | 100 | 100 | 100 | |
validator.ts | 14.29 | 100 | 0 | 14.29 | 2,3,4,5,7,8 |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.354s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/63531414
I'm having trouble making the following test pass:
import { useEffect, useState } from "react";
export function useComponentResources(required) {
const [componentResources, setComponentResources] = useState(null);
useEffect(() => {
if (required) {
// api call
setTimeout(() => setComponentResources({}), 100);
}
}, [required]);
return componentResources;
}
import { renderHook } from "#testing-library/react-hooks";
import { useComponentResources } from "./component-resources.hook";
describe("component-resources.hook", () => {
it("fetches resources when required", () => {
//act
const { result } = renderHook(() => useComponentResources(true));
//assert
expect(result.current).toEqual({});
});
});
It keeps failing:
expect(received).toEqual(expected)
Expected value to equal:
{}
Received:
null
Difference:
Comparing two different types of values. Expected object but received null.
7 | const { result } = renderHook(() => useComponentResources(true));
9 | //assert
> 10 | expect(result.current).toEqual({});
11 | });
12 | });
I have created a repro case in codesandbox:
https://codesandbox.io/embed/priceless-browser-94ec2
renderHook doesn't wait for your setTimeout to fire; it can't know what 'side effects' your component has. So when your expect() runs, the current value is still its default - null.
We can force the test to wait until the hook updates again by using waitForNextUpdate, which is on the object renderHook returns. waitForNextUpdate is a function that returns a promise that resolves once the hook is updated again (e.g. when your setTimeout fires).
import { renderHook } from "#testing-library/react-hooks";
import { useComponentResources } from "./component-resources.hook";
describe("component-resources.hook", () => {
it("fetches resources when required", async () => {
const { result, waitForNextUpdate } = renderHook(() => useComponentResources(true));
await waitForNextUpdate();
expect(result.current).toEqual({});
});
});