diff --git a/SUMMARY.md b/SUMMARY.md
index 3200c2826..86aa5d86e 100644
--- a/SUMMARY.md
+++ b/SUMMARY.md
@@ -49,6 +49,7 @@
* [hostNodes()](/docs/api/ShallowWrapper/hostNodes.md)
* [html()](/docs/api/ShallowWrapper/html.md)
* [instance()](/docs/api/ShallowWrapper/instance.md)
+ * [invoke(propName)](/docs/api/ShallowWrapper/invoke.md)
* [is(selector)](/docs/api/ShallowWrapper/is.md)
* [isEmpty()](/docs/api/ShallowWrapper/isEmpty.md)
* [isEmptyRender()](/docs/api/ShallowWrapper/isEmptyRender.md)
@@ -110,6 +111,7 @@
* [hostNodes()](/docs/api/ReactWrapper/hostNodes.md)
* [html()](/docs/api/ReactWrapper/html.md)
* [instance()](/docs/api/ReactWrapper/instance.md)
+ * [invoke(propName)](/docs/api/ReactWrapper/invoke.md)
* [is(selector)](/docs/api/ReactWrapper/is.md)
* [isEmpty()](/docs/api/ReactWrapper/isEmpty.md)
* [isEmptyRender()](/docs/api/ReactWrapper/isEmptyRender.md)
diff --git a/docs/api/ReactWrapper/invoke.md b/docs/api/ReactWrapper/invoke.md
new file mode 100644
index 000000000..9ec867ffa
--- /dev/null
+++ b/docs/api/ReactWrapper/invoke.md
@@ -0,0 +1,41 @@
+# `.invoke(propName)(...args) => Any`
+
+Invokes a function prop.
+
+#### Arguments
+
+1. `propName` (`String`): The function prop that is invoked
+2. `...args` (`Any` [optional]): Arguments that is passed to the prop function
+
+
+
+#### Returns
+
+`Any`: Returns the value from the prop function
+
+#### Example
+
+```jsx
+class Foo extends React.Component {
+ loadData() {
+ return fetch();
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+const wrapper = mount();
+wrapper.find('a').invoke('onClick')().then(() => {
+ // expect()
+});
+```
diff --git a/docs/api/ShallowWrapper/invoke.md b/docs/api/ShallowWrapper/invoke.md
new file mode 100644
index 000000000..2f9d32bde
--- /dev/null
+++ b/docs/api/ShallowWrapper/invoke.md
@@ -0,0 +1,40 @@
+# `.invoke(invokePropName)(...args) => Any`
+
+Invokes a function prop.
+
+#### Arguments
+
+1. `propName` (`String`): The function prop that is invoked
+2. `...args` (`Any` [optional]): Arguments that is passed to the prop function
+
+This essentially calls wrapper.prop(propName)(...args).
+
+#### Returns
+
+`Any`: Returns the value from the prop function
+
+#### Example
+
+```jsx
+class Foo extends React.Component {
+ loadData() {
+ return fetch();
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+const wrapper = shallow();
+wrapper.find('a').invoke('onClick')().then(() => {
+ // expect()
+});
diff --git a/docs/api/mount.md b/docs/api/mount.md
index a5bdb704b..c3401fb44 100644
--- a/docs/api/mount.md
+++ b/docs/api/mount.md
@@ -161,6 +161,9 @@ Returns the props of the root component.
#### [`.prop(key) => Any`](ReactWrapper/prop.md)
Returns the named prop of the root component.
+#### [`.invoke(propName)(...args) => Any`](ReactWrapper/invoke.md)
+Invokes a prop function on the current node and returns the function's return value.
+
#### [`.key() => String`](ReactWrapper/key.md)
Returns the key of the root component.
diff --git a/docs/api/shallow.md b/docs/api/shallow.md
index 77065a61a..0756bb06b 100644
--- a/docs/api/shallow.md
+++ b/docs/api/shallow.md
@@ -177,6 +177,9 @@ Returns the named prop of the current node.
#### [`.key() => String`](ShallowWrapper/key.md)
Returns the key of the current node.
+#### [`.invoke(propName)(...args) => Any`](ShallowWrapper/invoke.md)
+Invokes a prop function on the current node and returns the function's return value.
+
#### [`.simulate(event[, data]) => ShallowWrapper`](ShallowWrapper/simulate.md)
Simulates an event on the current node.
diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
index e9fefa260..f61d2e591 100644
--- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
@@ -1043,6 +1043,7 @@ describeWithDOM('mount', () => {
'hostNodes',
'html',
'instance',
+ 'invoke',
'is',
'isEmpty',
'isEmptyRender',
diff --git a/packages/enzyme-test-suite/test/shared/methods/invoke.jsx b/packages/enzyme-test-suite/test/shared/methods/invoke.jsx
new file mode 100644
index 000000000..844ae2217
--- /dev/null
+++ b/packages/enzyme-test-suite/test/shared/methods/invoke.jsx
@@ -0,0 +1,88 @@
+import React from 'react';
+import { expect } from 'chai';
+import sinon from 'sinon-sandbox';
+
+export default function describeInvoke({
+ Wrap,
+ WrapperName,
+}) {
+ describe('.invoke(propName)(..args)', () => {
+ class CounterButton extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { count: 0 };
+ }
+
+ render() {
+ const { count } = this.state;
+ return (
+
+
+
+ );
+ }
+ }
+
+ class ClickableLink extends React.Component {
+ render() {
+ const { onClick } = this.props;
+ return (
+
+ );
+ }
+ }
+
+ it('throws when pointing to a non-function prop', () => {
+ const wrapper = Wrap();
+
+ expect(() => wrapper.invoke('data-a')).to.throw(
+ TypeError,
+ `${WrapperName}::invoke() requires the name of a prop whose value is a function`,
+ );
+
+ expect(() => wrapper.invoke('does not exist')).to.throw(
+ TypeError,
+ `${WrapperName}::invoke() requires the name of a prop whose value is a function`,
+ );
+ });
+
+ it('can update the state value', () => {
+ const wrapper = Wrap();
+ expect(wrapper.state('count')).to.equal(0);
+ wrapper.find('button').invoke('onClick')();
+ expect(wrapper.state('count')).to.equal(1);
+ });
+
+ it('can return the handlers’ return value', () => {
+ const sentinel = {};
+ const spy = sinon.stub().returns(sentinel);
+
+ const wrapper = Wrap();
+
+ const value = wrapper.find('a').invoke('onClick')();
+ expect(value).to.equal(sentinel);
+ expect(spy).to.have.property('callCount', 1);
+ });
+
+ it('can pass in arguments', () => {
+ const spy = sinon.spy();
+
+ const wrapper = Wrap();
+
+ const a = {};
+ const b = {};
+ wrapper.find('a').invoke('onClick')(a, b);
+ expect(spy).to.have.property('callCount', 1);
+ const [[arg1, arg2]] = spy.args;
+ expect(arg1).to.equal(a);
+ expect(arg2).to.equal(b);
+ });
+ });
+}
diff --git a/packages/enzyme/src/ReactWrapper.js b/packages/enzyme/src/ReactWrapper.js
index ce44e22f5..d9512074e 100644
--- a/packages/enzyme/src/ReactWrapper.js
+++ b/packages/enzyme/src/ReactWrapper.js
@@ -827,6 +827,27 @@ class ReactWrapper {
return this.props()[propName];
}
+ /**
+ * Used to invoke a function prop.
+ * Will invoke an function prop and return its value.
+ *
+ * @param {String} propName
+ * @returns {Any}
+ */
+ invoke(propName) {
+ return this.single('invoke', () => {
+ const handler = this.prop(propName);
+ if (typeof handler !== 'function') {
+ throw new TypeError('ReactWrapper::invoke() requires the name of a prop whose value is a function');
+ }
+ return (...args) => {
+ const response = handler(...args);
+ this[ROOT].update();
+ return response;
+ };
+ });
+ }
+
/**
* Returns a wrapper of the node rendered by the provided render prop.
*
diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js
index c358fac71..99941e553 100644
--- a/packages/enzyme/src/ShallowWrapper.js
+++ b/packages/enzyme/src/ShallowWrapper.js
@@ -1295,6 +1295,27 @@ class ShallowWrapper {
return this.props()[propName];
}
+ /**
+ * Used to invoke a function prop.
+ * Will invoke an function prop and return its value.
+ *
+ * @param {String} propName
+ * @returns {Any}
+ */
+ invoke(propName) {
+ return this.single('invoke', () => {
+ const handler = this.prop(propName);
+ if (typeof handler !== 'function') {
+ throw new TypeError('ShallowWrapper::invoke() requires the name of a prop whose value is a function');
+ }
+ return (...args) => {
+ const response = handler(...args);
+ this[ROOT].update();
+ return response;
+ };
+ });
+ }
+
/**
* Returns a wrapper of the node rendered by the provided render prop.
*