Próbuję napisać prosty test dla prostego składnika reagowania, a ja chcę używać, aby potwierdzić, że funkcja została wywołana, gdy symuluję kliknięcie z enzymem. Według doktorów, powinienem być w stanie użyć spyOn, aby to zrobić: Spyon.

Jednak kiedy spróbuję tego, coraz robię TypeError: Cannot read property '_isMockFunction' of undefined, co uważam, że mój szpieg jest niezdefiniowany. Mój kod wygląda tak:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {

  myClickFunc = () => {
      console.log('clickity clickcty')
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

W moim pliku testowym:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { shallow, mount, render } from 'enzyme'

describe('my sweet test', () => {
 it('clicks it', () => {
    const spy = jest.spyOn(App, 'myClickFunc')
    const app = shallow(<App />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(spy).toHaveBeenCalled()
 })
})

Czy ktoś ma wgląd w to, co robię źle?

66
Max Millington 27 czerwiec 2017, 01:10

3 odpowiedzi

Najlepsza odpowiedź

Prawie skończyłeś bez zmian oprócz tego, jak spyOn. Kiedy używasz szpiegowania, masz dwie opcje: spyOn App.prototype lub komponent component.instance().

const spy = jest.Spyon (class.prototyp, "Metoda")

Kolejność dołączenia szpiegowania na prototypu i renderowaniu klasie (płytkie renderowanie) Twoja instancja jest ważna.

const spy = jest.spyOn(App.prototype, "myClickFn");
const instance = shallow(<App />);

Bit App.prototype w pierwszej linii jest to, czego potrzebujesz, aby sprawić, że rzeczy działają. JavaScript class nie ma żadnej z jego metod, dopóki nie będziesz niszczyć go za pomocą new MyClass() lub zanurzysz w MyClass.prototype. W przypadku twojego konkretnego pytania po prostu potrzebne do szpiegowania metody App.prototype myClickFn.

jest.spyon (komponent.Instance (), "Metoda")

const component = shallow(<App />);
const spy = jest.spyOn(component.instance(), "myClickFn");

Ta metoda wymaga instancji shallow/render/mount React.Component, aby była dostępna. Zasadniczo spyOn po prostu szuka czegoś do porwania i wepchnąć w jest.fn(). Mogłoby być:

Zwykły 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.

Lub React.Component instance:

const component = shallow(<App />);
/*
component.instance()
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(component.instance(), "myClickFn");

Lub 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.

Użyłem i widziałem obie metody. Gdy mam blok beforeEach() lub beforeAll(), mogę iść z pierwszym podejściem. Jeśli po prostu potrzebuję szybkiego szpiegowania, użyję drugiego. Dopważaj kolejność dołączenia szpiegowania.

EDYTOWAĆ: Jeśli chcesz sprawdzić efekty uboczne swojego myClickFn, można go wywołać w osobnym teście.

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");
*/

EDYTOWAĆ: Oto przykład korzystania z komponentu funkcjonalnego. Należy mieć na uwadze, że wszelkie sposoby skrócone w komponencie funkcjonalnym nie są dostępne do szpiegowania. Byłbyś szpiegujący rekwizyty funkcji przekazywane do twojego funkcjonalnego składnika i testowanie wywołania tych. Ten przykład bada zastosowanie jest.fn() w przeciwieństwie do jest.spyOn, z których oba dzieli się API funkcji Mock. Chociaż nie odpowiada na pierwotne pytanie, nadal zapewnia wgląd w inne techniki, które mogłyby odpowiadać przypadkom pośrednio związanym z pytaniem.

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*/);
68
Willi Mentzel 16 grudzień 2020, 09:46

W swoim kodzie testowym próbujesz przejść App do funkcji Spyon, ale Spyon będzie działał tylko z obiektami, a nie klasami. Ogólnie rzecz biorąc, musisz tutaj użyć jednego z dwóch podejść:

1) W przypadku gdy kliknięcie kliknięcia wywołuje funkcję przekazaną jako rekwizyt, np.

class App extends Component {

  myClickFunc = () => {
      console.log('clickity clickcty');
      this.props.someCallback();
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

Możesz teraz przejść w funkcji szpiegowskiej jako rekwizyt dla komponentu, a potwierdza, że nazywa się to:

describe('my sweet test', () => {
 it('clicks it', () => {
    const spy = jest.fn();
    const app = shallow(<App someCallback={spy} />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(spy).toHaveBeenCalled()
 })
})

2) gdzie obsługiwacz kliknij ustawia jakiś stan na komponencie, np.

class App extends Component {
  state = {
      aProperty: 'first'
  }

  myClickFunc = () => {
      console.log('clickity clickcty');
      this.setState({
          aProperty: 'second'
      });
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

Możesz teraz dokonywać twierdzeń o stanie komponentu, tj.

describe('my sweet test', () => {
 it('clicks it', () => {
    const app = shallow(<App />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(app.state('aProperty')).toEqual('second');
 })
})
13
Alex Young 26 czerwiec 2017, 23:55

Jesteś prawie na miejscu. Chociaż zgadzam się z @alex Young Odpowiedz na temat korzystania z rekwizytów, po prostu potrzebujesz odniesienia do instance przed próba szpiegowania na metodzie.

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()
 })
})

Dokumenty: http://airbnb.io/enzyme/docs/api/shallowwrapper/instance.html.

22
ilrein 17 październik 2017, 16:57