You need to set up the spy before the method is called. Jasmine spys wrap function to determine when they are called and what they get called with. The method must be wrapped before the spied on method is called in order to capture the information. Try changing your test to match the following:
it('should foobar', () => {
spyOn(component, 'bar');
component.foo();
expect(component.bar).toHaveBeenCalled();
})
Answer from Teddy Sterne on Stack Overflowangular - testing that a component method calls another method - Stack Overflow
reactjs - Spying on React functional component method with jest and enzyme; Cannot spyOn on a primitive value - Stack Overflow
How to spyOn method inside prop passed down to a component using Jest?
spy on component method failed
Videos
You need to set up the spy before the method is called. Jasmine spys wrap function to determine when they are called and what they get called with. The method must be wrapped before the spied on method is called in order to capture the information. Try changing your test to match the following:
it('should foobar', () => {
spyOn(component, 'bar');
component.foo();
expect(component.bar).toHaveBeenCalled();
})
it("should foobar", () => {
const spy = spyOn(component, "bar");
component.foo();
expect(spy).toHaveBeenCalled();
});
The error means, the function sampleMethod you defined inside the functional component SampleComponent is not defined in SampleComponent.prototype. So SampleComponent.prototype.sampleMethod is undefined, jest can't spy on a undefined value.
So the correct way to test sampleMethod event handler is like this:
index.spec.tsx:
import React from 'react';
import SampleComponent from './';
import { shallow } from 'enzyme';
describe('SampleComponent', () => {
test('should handle click correctly', () => {
const logSpy = jest.spyOn(console, 'log');
const wrapper = shallow(<SampleComponent></SampleComponent>);
const button = wrapper.find('button');
expect(button.text()).toBe('Click Me');
button.simulate('click');
expect(logSpy).toBeCalledWith('hello world');
});
});
We can spy on console.log, to assert it is to be called or not.
Unit test result with 100% coverage:
PASS src/react-enzyme-examples/02-react-hooks/index.spec.tsx
SampleComponent
✓ should handle click correctly (19ms)
console.log node_modules/jest-mock/build/index.js:860
hello world
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.036s
Dependencies version:
"react": "^16.11.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"jest": "^24.9.0",
"jest-environment-enzyme": "^7.1.1",
"jest-enzyme": "^7.1.1",
sample.js
import * as React from 'react';
export let util = {sampleMethod: null };
const SampleComponent = () => {
util.sampleMethod = () => {
console.log('hello world');
};
return <button onClick={sampleMethod} type="button">Click Me</button>;
};
export default SampleComponent;
sample.test.js
import { shallow } from 'enzyme';
import SampleComponent, {util} from './sample';
test('testing spy', () => {
const spy = jest.spyOn( util, 'sampleMethod' );
const wrapper = shallow(<SampleComponent />);
wrapper.find('button').simulate('click');
expect(spy).toHaveBeenCalled(1);
});
I know I'm late to answer but I think this would help some other developers also
Just to add to the answer above of @user2718281, SetMethods it's deprecated, so you better define the spyOn pointing to the ComponentB before instantiating like this:
// create a spy before the instance is created
const spySomeMethod = jest.spyOn(ComponentB.methods, 'someMethod')
const spyUpdate = jest.spyOn(ComponentB.methods, 'update')
const wrapper = shallowMount(ComponentB, { propsData: { ... } });
// your tests ...
// verify the spy was called
expect(spyUpdate).toHaveBeenCalled();
// remove the spy
spyUpdate.mockReset();
And about the question maybe you are forgetting to add the ComponentA.methods like this:
const spySomeMethod = jest.spyOn(ComponentB.methods, 'someMethod')
but if you want to test some lifecycle method event like mounted or created remove the .methods like this:
const spySomeMethod = jest.spyOn(ComponentB, 'created')
ComponentA is a component definition with update as a child of the methods attribute so update will not be found on ComponentA or ComponentB. spyOn should be applied on the instance of the component instead.
const wrapper = shallowMount(ComponentB, { propsData: { ... } });
// create a spy on the instance method
const spyUpdate = jest.spyOn(wrapper.vm, 'update')
// replace the instance method with the spy
wrapper.setMethods({ update: spyUpdate });
// your tests ...
// verify the spy was called
expect(spyUpdate).toHaveBeenCalled();
// remove the spy
spyUpdate.mockReset();
You were almost done without any changes besides how you spyOn.
When you use the spy, you have two options: spyOn the App.prototype, or component component.instance().
const spy = jest.spyOn(Class.prototype, "method")
The order of attaching the spy on the class prototype and rendering (shallow rendering) your instance is important.
const spy = jest.spyOn(App.prototype, "myClickFn");
const instance = shallow(<App />);
The App.prototype bit on the first line there are what you needed to make things work. A JavaScript class doesn't have any of its methods until you instantiate it with new MyClass(), or you dip into the MyClass.prototype. For your particular question, you just needed to spy on the App.prototype method myClickFn.
jest.spyOn(component.instance(), "method")
const component = shallow(<App />);
const spy = jest.spyOn(component.instance(), "myClickFn");
This method requires a shallow/render/mount instance of a React.Component to be available. Essentially spyOn is just looking for something to hijack and shove into a jest.fn(). It could be:
A plain object:
const obj = {a: x => (true)};
const spy = jest.spyOn(obj, "a");
A class:
class Foo {
bar() {}
}
const nope = jest.spyOn(Foo, "bar");
// THROWS ERROR. Foo has no "bar" method.
// Only an instance of Foo has "bar".
const fooSpy = jest.spyOn(Foo.prototype, "bar");
// Any call to "bar" will trigger this spy; prototype or instance
const fooInstance = new Foo();
const fooInstanceSpy = jest.spyOn(fooInstance, "bar");
// Any call fooInstance makes to "bar" will trigger this spy.
Or a React.Component instance:
const component = shallow(<App />);
/*
component.instance()
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(component.instance(), "myClickFn");
Or a React.Component.prototype:
/*
App.prototype
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(App.prototype, "myClickFn");
// Any call to "myClickFn" from any instance of App will trigger this spy.
I've used and seen both methods. When I have a beforeEach() or beforeAll() block, I might go with the first approach. If I just need a quick spy, I'll use the second. Just mind the order of attaching the spy.
EDIT:
If you want to check the side effects of your myClickFn you can just invoke it in a separate test.
const app = shallow(<App />);
app.instance().myClickFn()
/*
Now assert your function does what it is supposed to do...
eg.
expect(app.state("foo")).toEqual("bar");
*/
EDIT:
Here is an example of using a functional component. Keep in mind that any methods scoped within your functional component are not available for spying. You would be spying on function props passed into your functional component and testing the invocation of those. This example explores the use of jest.fn() as opposed to jest.spyOn, both of which share the mock function API. While it does not answer the original question, it still provides insight on other techniques that could suit cases indirectly related to the question.
function Component({ myClickFn, items }) {
const handleClick = (id) => {
return () => myClickFn(id);
};
return (<>
{items.map(({id, name}) => (
<div key={id} onClick={handleClick(id)}>{name}</div>
))}
</>);
}
const props = { myClickFn: jest.fn(), items: [/*...{id, name}*/] };
const component = render(<Component {...props} />);
// Do stuff to fire a click event
expect(props.myClickFn).toHaveBeenCalledWith(/*whatever*/);
If a functional component is niladic (no props or arguments) then you can use Jest to spy on any effects you expect from the click method:
import { myAction } from 'src/myActions'
function MyComponent() {
const dispatch = useDispatch()
const handleClick = (e) => dispatch(myAction('foobar'))
return <button onClick={handleClick}>do it</button>
}
// Testing:
const { myAction } = require('src/myActions') // Grab effect actions or whatever file handles the effects.
jest.mock('src/myActions') // Mock the import
// Do the click
expect(myAction).toHaveBeenCalledWith('foobar')
You're almost there. Although I agree with @Alex Young answer about using props for that, you simply need a reference to the instance before trying to spy on the method.
describe('my sweet test', () => {
it('clicks it', () => {
const app = shallow(<App />)
const instance = app.instance()
const spy = jest.spyOn(instance, 'myClickFunc')
instance.forceUpdate();
const p = app.find('.App-intro')
p.simulate('click')
expect(spy).toHaveBeenCalled()
})
})
Docs: http://airbnb.io/enzyme/docs/api/ShallowWrapper/instance.html
Your validateBeforeSave function is declared within SomeComponent making it a closed/private scope function not accessible outside. You can pass that function as a prop and you can then create spy and pass it as a prop value in your test and test for if the prop function passed (spy) was called or not
So you would modify your function somewhat like this:
// some validator function
function validateBeforeSave(){
...
}
// Some function Component
const SomeComponent = (props: IMyComponentProps) => {
const { classes, validateBeforeSave } = props;
// Component has state
const [count, setCount] = useState(0);
function handleClick() {
validateBeforeSave();
.
.
.
}
return (
<div>
<Button>
className="saveBtn"
onClick={handleClick}
</Button>
</div>
);
};
And In your Unit test, something like this:
// Unit test
describe('SomeComponent' () => {
it('validates model on button click', () => {
const validateSpy = jest.fn();
const wrapper = mount(
<MuiThemeProvider theme={theme}>
<SomeComponent validateSpy={validateSpy}/>
</MuiThemeProvider>,
);
const instance = wrapper.instance();
wrapper
.find('.saveBtn')
.at(0)
.simulate('click');
expect(validateSpy).toHaveBeenCalledTimes(1);
});
}
I had similar problem mocking callback prop method with React 16.x.x, enzyme instance method returns null, what you can do is pass directly jest.fn() as a prop.
EXAMPLE:
it('should invoke callback with proper data upon checkbox click', () => {
const spyCheckboxClick = jest.fn((id, item) => ({
id,
item,
}))
const component: any = enzyme.mount(
<SectionColumn {...{
...mockProps,
onCheckboxClick: spyCheckboxClick,
}} />
);
expect(spyCheckboxClick).toHaveBeenCalledTimes(0);
// perform click to checkbox
const checkboxComponent = component.find('StyledCheckbox');
const input = checkboxComponent.first().children();
input.simulate('change');
expect(spyCheckboxClick).toHaveBeenCalledTimes(1);
expect(spyCheckboxClick()).toEqual(null)
});