2

I was trying to test my components and everytime my tests fail, and i'm not able to figure out where the problem is located.

Login.test.js :

import { Meteor } from 'meteor/meteor';
import React from 'react';
import expect from 'expect';
import { mount } from 'enzyme';

import { Login } from './Login';

if (Meteor.isClient) {
    describe("Login", function() {
        it("should show error messages", function() {
            const error = "This is not working";
            const wrapper = mount(<Login loginWithPassword={() => {}}/>);

            wrapper.setState({ error: error });

            expect(wrapper.find("p").text()).toBe(error);

            wrapper.setState({ error: "" });
            expect(wrapper.find("p").length).toBe(0);
        });

        it("should called loginWithPassword with the form data", function() {
            const email = "test@test.com";
            const password = "password1234";
            const spy = expect.createSpy();
            const wrapper = mount(<Login loginWithPassword={spy}/>);

            wrapper.ref("email").node.value = email;
            wrapper.ref("password").node.value = password;
            wrapper.find("form").simulate("submit");

            expect(spy.calls[0].arguments[0]).toEqual({email: email});
            expect(spy.calls[0].arguments[1]).toEqual(password);
        });

        it("should set loginWithPassword callback errors", function() {

        });
    })
}

Login.js :

import React from "react";
import { Link, Redirect } from "react-router-dom";
import { Meteor } from "meteor/meteor";
import { createContainer } from 'meteor/react-meteor-data';

export class Login extends React.Component {
  constructor(props) {
    super(props);

    this.onSubmit = this.onSubmit.bind(this);

    this.state = {
      error: ""
    };
  }
  onSubmit(e) {
    e.preventDefault();
    let email = this.refs.email.value.trim();
    let password = this.refs.password.value.trim();

    this.props.loginWithPassword({ email }, password, err => {
      if (err) {
        this.setState({ error: "Unable to login. Check your credentials." });
      } else {
        this.setState({ error: "" });
      }
    });
  }
  render() {
    return (
      <div className="boxed-view">
        <div className="boxed-view__box">
          <h1>Login</h1>

          {this.state.error
            ? <p className="boxed-view__box__error">{this.state.error}</p>
            : undefined}

          <form
            onSubmit={this.onSubmit}
            noValidate
            className="boxed-view__form"
          >
            <input type="email" ref="email" name="email" placeholder="Email" />
            <input
              type="password"
              ref="password"
              name="password"
              placeholder="Password"
            />
            <button className="button">Login</button>
          </form>
          <Link to="/signup">Not registered yet?</Link>
        </div>
      </div>
    );
  }
}

Login.PropTypes = {
  loginWithPassword: React.PropTypes.func.isRequired
};

export default createContainer(() => {
  return {
    loginWithPassword: Meteor.loginWithPassword
  }
}, Login);

Mocha client and server test :

Screenshot Chrome Mocha tests

Thank you for helping me out!

Kaldrogh
  • 73
  • 1
  • 2
  • 4

2 Answers2

5

it's a shame that nobody answered this yet.

Hopefully you've eventually realized that the issue is the result of React Router's component rendering outside of the context of a Router component.

You need to wrap your component in a component to resolve this and then make the appropriate changes as your wrapper variable will change and your component will no longer be the root component, which will change how you operate on the wrapper variable.

Kevin
  • 1,403
  • 2
  • 11
  • 15
2

You have a couple of issues with your code, Please use the following & let me know if you still have any problems

import { Meteor } from 'meteor/meteor';
import React from 'react';

import expect from 'expect';

import { configure } from 'enzyme';
import { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';

configure({ adapter: new Adapter() });

import { Login } from './Login';
//notice we are importing the "raw" named-export Login not the containerized default one.

if (Meteor.isClient) {
  describe('Login', () => {
    it('Should show error messages', () => {
      const error = 'This is not working';
      const wrapper = mount(<Login loginWithPassword={() => { }} />);

      wrapper.setState({ error: error });

      expect(wrapper.find('p').text()).toBe(error);

      wrapper.setState({error: ''});

      expect(wrapper.find('p').length).toBe(0);
    });

    it('should call loginWithPassword with the form data', () => {
      const email = 'helcode@example.com';
      const password = 'password123';
      const spy = expect.createSpy();
      const wrapper = mount (<Login loginWithPassword={spy}/>);

      //notice the change, node ==> instance() due to deprecation of use of private properties by enzyme.
      wrapper.find('input[name="email"]').instance().value = email;
      wrapper.find('input[name="password"]').instance().value = password;


      wrapper.find('form').simulate('submit');

      expect(spy.calls[0].arguments[0]).toEqual({email:email});
      expect(spy.calls[0].arguments[1]).toBe(password);
      //above we used toBe (instead of toEqual) because second argument is a variable not object
    });
  });
}

Summary of issues in the original code

[1] Use of node in referencing DOM objects as in the below snippet is deprecated by enzyme wrapper.ref("email").node.value = email;

Furthermore, React has deprecated the use of string refs. You should use callbacks to avoid console warnings.

Hence, The right way is to use instance() and find() as per the below snippet wrapper.find('input[name="email"]').instance().value = email;

[2] declaration in the original code defined email as object and password as variable (snippet below) this.props.loginWithPassword({ email }, password, err => {

Hence, we should pick the right assertion method from expect to handle each case as per the below snippet

expect(spy.calls[0].arguments[0]).toEqual({email:email});
expect(spy.calls[0].arguments[1]).toBe(password);
helcode
  • 1,859
  • 1
  • 13
  • 32
  • Welcome to Stack Oveflow! It could be nice to explain which changes did you made to make it easier to understand. –  Nov 24 '17 at 23:41
  • Thanks @FranciscodelaPeña, I have highlighted all the changes via comments within the code. Will break them into comments in a minute. – helcode Nov 25 '17 at 21:22
  • I have modified my answer to reflect issues in the original code. enjoy... – helcode Nov 25 '17 at 21:33