Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing component methods #208

Closed
mxstbr opened this issue Feb 22, 2016 · 62 comments
Closed

Testing component methods #208

mxstbr opened this issue Feb 22, 2016 · 62 comments

Comments

@mxstbr
Copy link
Contributor

mxstbr commented Feb 22, 2016

I couldn't find any information on this, if this is a duplicate excuse me for stealing your time.

I'm trying to test this application, but I ran into a wall when testing component methods. As an example, take this component:

class Button extends React.Component {
  handleClick() {
    // Do something here
  }
  render() {
    // Component here
  }
}

How could I test the handleClick method here when shallowly rendering this component? (The same question applies to lifecycle methods)

@ljharb
Copy link
Member

ljharb commented Feb 22, 2016

@mxstbr const wrapper = shallow(<Button />); wrapper.instance().handleClick()

@mxstbr
Copy link
Contributor Author

mxstbr commented Feb 22, 2016

That is so nice, thanks for the fast response @ljharb!

@mxstbr mxstbr closed this as completed Feb 22, 2016
@machineghost
Copy link

Dumb question: is there a new way to do this? When I call shallow(<MyComponent/>) the thing that gets returned has no "instance` method; instead it looks like this:

{ '$$typeof': Symbol(react.element),
  type: 'div',
  key: null,
  ref: null,
  props: { className: 'fool', children: [ [Object], [Object] ] },
  _owner: null,
  _store: {} }

@ljharb
Copy link
Member

ljharb commented Jul 30, 2016

@machineghost that's the console inspection which shows enumerable properties. Try actually checking .instance on it.

@machineghost
Copy link

machineghost commented Jul 30, 2016

Thanks for the quick response, but it's not there for me. I get this:

TypeError: choices.instance is not a function

The real code is something like:

let Choices = React.createClass({
    _buildChoices() { ....}
    ....
}
choices = shallowRender(<Choices step={5}/>);
var buttons = choices.instance()._buildChoices();
// get the above error

P.S. I know the message says it's not a function (implying that it might be an object or something), but if I console.log(choices.instance) it's undefined.

@ljharb
Copy link
Member

ljharb commented Jul 30, 2016

Hmm - what version of enzyme and React are you using?

@machineghost
Copy link

"enzyme": "^2.4.1",
"react": "^0.14.3",

@ljharb
Copy link
Member

ljharb commented Jul 30, 2016

And how do you import "shallowRender"?

@machineghost
Copy link

Oh dear lord I'm a moron. There was a mistake in my import, and I was actually using a different shallowRender (a home-grown one from pre-Enzyme) without realizing it.

Thank you SO much, and sorry for the stupidity.

@nikunjgami1991
Copy link

nikunjgami1991 commented Dec 30, 2016

Hi

I have tried below solution for calling methods of component and it is working fine for components having state.

But, I have one stateless component and I want to test one method of that component ans I am getting below error.

//Error
` TypeError: wrapper.instance(...).setFocusToTextBox is not a function

  at Object.<anonymous> (src\Engines\LeadershipTrait\__test__\RatingInput.test.js:69:28)

`

// Stateless component method
` const setFocusToTextBox = (index) => {

 window.setTimeout(function () {
        let textbox = document.getElementById("rating"+props.index+index);                    
        textbox.focus();
    },0);
}`

// Testcase
` /*Checking setToFocus method/
it('Checking setToFocus method', () => {

     let enteredRating = [[1,0,0,0,0,0]];        
    //render component for even number row with selectedRating
    let wrapper = shallow(<RatingInput index={oddIndex} ratingEntered={enteredRating} rating={rating}/>);
    wrapper.instance().setFocusToTextBox(1);        
});`

Please guide where i am doing mistake.

Thank you,
Nikunj.

@ljharb
Copy link
Member

ljharb commented Dec 30, 2016

Stateless components can't possibly have any methods, because they're not instances. That's just a function closed inside the SFC, and it's impossible to invoke it from tests unless you extract it from the rendered jsx.

@nikunjgami1991
Copy link

Thanks for guidance.

@retrofox
Copy link

retrofox commented Feb 7, 2017

I'm having trouble when I try to get the wrapper instance for a component which is written using class.

class MyComponent extends Component {
  myMethod() {
    // ...
  }
}

// on test files
const wrapper = shallow( <MyComponent /> ).instance();

wrapper.myMethod() // -> undefined

In short, the wrapper instance doesn't have the methods of the Component.
Am I doing something wrong?

@ljharb
Copy link
Member

ljharb commented Feb 7, 2017

@retrofox that seems to work - if it didn't have the method, it would throw a TypeError. Returning undefined might be what myMethod does?

@retrofox
Copy link

retrofox commented Feb 7, 2017

@retrofox that seems to work - if it didn't have the method, it would throw a TypeError. Returning undefined might be what myMethod does?

Nop. The method is not defined.

TypeError: wrapper.myMethod is not a function
      at Context.<anonymous> (index.jsx:81:8)

@aweary
Copy link
Collaborator

aweary commented Feb 7, 2017

@retrofox what does instance() return?

@retrofox
Copy link

retrofox commented Feb 7, 2017

sorry @aweary. I was wrapping the MyCompnent component with a translation helper. I don't know exactly what is the issue in our code but definitely I'll have to dive on this.
Thanks for your comments and I'm sorry for disturbing.

@smoholkar
Copy link

@ljharb I saw this in one of your comments above.
const wrapper = shallow(); wrapper.instance().handleClick()

I am working on a similar issue:
How do I get the correct value of a method() if the method returns a different value on componentWillReceiveProps?

I tried to do the following

const component = shallow(<Num {...properties} />);
component.instance().getNumberDetails() --> should return 1000.00 which it does
component.update()
component.instance().getNumberDetails() --> should return $1,000.00 but it doesn't seem to be updated. 

(I might have to setState explicitly here before component.update()? But I am testing for the value itself. What's the right way to test this? )

@ljharb
Copy link
Member

ljharb commented Feb 22, 2017

@smoholkar i'll have to see the implementation of Num to answer that.

@smoholkar
Copy link

smoholkar commented Feb 22, 2017

@ljharb It's huge but this is the skeleton of it :
``

class Num extends React.Component {

state = {
  value = this.props.path || ''
}

componentWillReceiveProps(newProps) {
    if (newProps.path !== undefined && this.props.path !== newProps.path) {
      this.setState({ value: newProps.path });
    }
}

getNumberDetails() {
// ......
// does some processing & then returns the value
returns value 
}

render() {
  const result = getNumberDetails() // I'm testing for correctness of the value of result. 
  
return (
   <Wrapper1 value = {result}>
        <Wrapper2 value = {result}> 
        </Wrapper2>
   <Wrapper1>
)

}


@ljharb
Copy link
Member

ljharb commented Feb 22, 2017

It seems like you want to setProps and update your wrapper, and then before and after, assert on .prop('value')

@HelgeWieding
Copy link

Hey guys,

I have the problem that i mounted a redux connected component. When i call .instance() on it i do not have access to the class functions.
I also cannot shallow render it because the component has a lot of initialization code in the constructor.

@ljharb
Copy link
Member

ljharb commented Apr 7, 2017

@HelgeWieding use .dive() to dive through the wrapper.

@palaniichukdmytro
Copy link

Hi guys, I use mocha-enzyme-chai-sinon to testing the react router v4. It's really challenging for me to write a unit test for router v4. So I need to test is redirect really. You can check my routes.js http://stackoverflow.com/questions/43604837/react-router-v4-unit-test

`

    describe('<Routes/>', () => {

      let routes
      let redirectToLoginPage
      let login

     beforeEach(() => routes = shallow(<Routes redirectToLoginPage={redirectToLoginPage = sinon.spy()} login={login = sinon.spy()}/>))

     it.only('should login on entering /login-callback or redirect to / ', () => {
      let result = routes.find(Route).findWhere(x => x.props().path === '/login-callback').props().render()
   
       login.calledOnce.should.be.true
       result.find(Redirect).length.should.be.equal(1)
       result.props.to.should.be.equal('/')

})`

But I catch the error in the console the find is not a function. When I console result, I see what that expect. how to test the [Function: Redirect] - really redirect

{ '$$typeof': Symbol(react.element),
  type:
   { [Function: Redirect]
     propTypes: { push: [Object], from: [Object], to: [Object] },
     defaultProps: { push: false },
     contextTypes: { router: [Function: bound checkType] } },
  key: null,
  ref: null,
  props: { to: '/', push: false },
  _owner: null,
  _store: {} }

@palaniichukdmytro
Copy link

I solve the main problem, but why I receive the
expected to be of type [Function: Redirect], but it is of type null
expect(result).to.have.type(Redirect)

@ljharb
Copy link
Member

ljharb commented Apr 26, 2017

@palaniichukdmytro your comments here seem duplicates of your comments on #736.

@ljharb
Copy link
Member

ljharb commented Dec 23, 2017

ah, yes, that is true. However, componentWillMount executes as part of the first render, so any props it needs should be passed in regardless.

(You should also update to enzyme 3 :-) )

@derwaldgeist
Copy link

Is there any good tutorial on how to use all this dive(), find() and instance() stuff? I am scratching my head for hours now to get this work in a non-trivial scenario, where components are wrapped in Redux stores and IntlProviders but I have to call one of their methods. This API is really pretty complex.

@FDiskas
Copy link

FDiskas commented Jan 22, 2018

You can check here
https://github.com/nfq-eta/react-typescript/blob/a7e6eb2e4e5191833454a9e29d314d64107e97ad/src/client/containers/tests/AppContainer.test.tsx#L1

@shahzaibkhalid
Copy link

Worked perfectly. Thanks for this.

@ashraf1994
Copy link

how to mock a method of component which uses store.
wrapper.instance.method() is not working for my componnt as it is using Store

@FDiskas
Copy link

FDiskas commented Feb 17, 2018

@ashraf1994 export your component without a connection
export { App as AppDisconnected };
You can check example
https://github.com/nfq-eta/react-typescript/blob/a7e6eb2e4e5191833454a9e29d314d64107e97ad/src/client/containers/AppContainer.tsx#L90

@ljharb
Copy link
Member

ljharb commented Feb 20, 2018

I don't recommend doing that; if it's never used in production without the connection, it should never be tested without the connection.

@FDiskas
Copy link

FDiskas commented Feb 20, 2018

All data in tests should be mocked including store. So this is more easy todo so. Export disconnected component and pass all required props that comes from store and test them.

@ljharb
Copy link
Member

ljharb commented Feb 23, 2018

It's easier, but it's still wrong. Prefer correctness over convenience.

@FDiskas
Copy link

FDiskas commented Feb 23, 2018

You mean I should write tests like

const something = () => 2*2
expect(2*2).toEqual(something());

It's wrong. You should not add result as expectancy criteria. Do it like

const something = () => 2*2
expect(something()).toEqual(4);

You are probably talking about an integration tests is used to check that different pieces of the system work together. Integration tests cover whole applications.

But this is not the right place to discuss.

Peace 📣

@rodoabad
Copy link

rodoabad commented May 8, 2018

@ljharb let's assume for a minute that this is the given state of the component.

class Button extends React.Component {
  handleClick = () => {
    // make props.answer = 2 through magic
  }
  render = () => (
      <div onClick={this.handleClick}/>
  )
}

Wouldn't it be better to test by simulating the click if and only if you know it's hooking to the click event?

it('should do something after user clicks the button', () => {
  const wrapper = render(<Button/>);
  
  wrapper.simulate('click');
  
  expect(props.answer).toEqual(2);
});

The example you provided above makes a lot of sense since you do not know the behaviour to test yet so you have to go back to your implementation details and test it.

By going above one more level and simulating the click instead of invoking the method in that specific instance, you can safely refactor handleClick and still be able to test the behaviour that you want.

class Button extends React.Component {
-  handleClick = () => {
+  somethingElse = () => {
    // make props.answer = 2;
  }
  render = () => (
-      <div onClick={this.handleClick}/>
+      <div onClick={this.somethingElse}/>
  )
}

If a refactor (no behaviour changes) happens then my test is still fine. Makes sense because I am testing the behaviour of the component (not the method as OP mentioned).

There's nothing wrong with your example btw. Yours will be invoking the implementation since you do not know which one handleClick will be hooking on i.e. I can very well do something like onChange={handleClick}. I'm simply showing a different way to test when you get more details.

@andynhi
Copy link

andynhi commented Jun 19, 2018

@retrofox i don't know if you resolved the issue - a year later lol but i had to dive() into my wrapper before executing the instance, after that my class methods were available

@karolisgrinkevicius

This comment has been minimized.

@ljharb

This comment has been minimized.

@FDiskas

This comment has been minimized.

@ljharb

This comment has been minimized.

@subrat7
Copy link

subrat7 commented Aug 23, 2018

How to test below method

 function getDateTimeFormatted(value){
    if (typeof value == "undefined") { return "" ; }
   return moment.utc(value.valueOf()).locale("en").format("MM/DD/YYYY hh:mm:ss A");
}

@karolisgrinkevicius
Copy link

@subrat7 this article would clarify out things about mocking date and momentjs in particular.

johnpmitsch referenced this issue in jturel/katello Sep 19, 2018
@Exponent500
Copy link

In case anyone else has this issue, I simply had to dive in order to get it to work.

test('StatefulContainer has custom business logic', () => {
  const wrapper = shallow(
    <StatefulContainer store={store} />
  );
  const result = wrapper.dive().instance()._privateFunc()
  expect(result).toEqual(2);
});

But where is your definition of store? Would love to know how that looks...

@wriozumi
Copy link

@mxstbr const wrapper = shallow(<Button />); wrapper.instance().handleClick()

what is instance()

@ljharb
Copy link
Member

ljharb commented May 27, 2019

@42b883 an enzyme method that exposes the underlying class component instance.

@wriozumi
Copy link

wriozumi commented May 27, 2019 via email

@sauron918
Copy link

sauron918 commented Apr 22, 2021

const Button: React.FC = (props) =>  {
  const handleClick = () => {
    // Do something here
  }
  return( 
    // Component here
  );
}

wrapper.instance().handleClick() – seems it won't work for Functional Components

@FDiskas
Copy link

FDiskas commented Apr 22, 2021

You should be able to access methods without calling instance

@notKvS
Copy link

notKvS commented Oct 20, 2021

const Button: React.FC = (props) =>  {
  const handleClick = () => {
    // Do something here
  }
  return( 
    // Component here
  );
}

wrapper.instance().handleClick() – seems it won't work for Functional Components

Most probably its because with React 16 and above, instance() returns null for stateless functional components.

@ljharb
Copy link
Member

ljharb commented Oct 20, 2021

Also, in that case there is no instance conceptually - closed-over variables can’t be accessed from outside the scope they’re defined in unless they’re exposed explicitly. enzyme can’t violate how JavaScript itself works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests