1

I want to test that user should not navigate to any other screen if TextInput equals to "".

I have tried to put console logs to understand more correctly but the problem is now that, after input.simulate("changeText", "any@email.com"); happened correctly, console log says that incoming value from test is "any@email.com" but after setValue(text); happened console.log says that useState value is "".

Why state is not changing? Do I need to update the wrapper with .update() ?

Here is my React Native code:

import React, { useState } from "react";
import { View, Button, TextInput } from "react-native";

export const LoginContainer = (props) => {
  const [value, setValue] = useState("");

  const handleClick = () => {
    if (value !== "") {
      props.navigation.navigate("any other screen");
    }
  }

  return (
    <View>
      <TextInput
        test-id="login-input"
        onChangeText={(text) => {
          console.log("INPUT_TEXT:", text);
          setValue(text);
          console.log("STATE_TEXT:", value);
        }}
        value={value}
      />
      <Button test-id="login-button" onPress={() => handleClick()} />
    </View>
  );
}

Console log:

  console.log
    INPUT_TEXT: any@email.com

      at onChangeText (....)

  console.log
    STATE_TEXT: 

      at onChangeText (....)

  expect(jest.fn()).toHaveBeenCalledTimes(expected)

  Expected number of calls: 1
  Received number of calls: 0

Test code:

import "react-native";
import React from "react";
import { shallow } from 'enzyme';
import { LoginContainer } from "...";
import { findByTestAttr } from '...';

const navigation = {
  navigate: jest.fn()
}

describe('correct login action', () => {
    const wrapper = shallow(<LoginContainer navigation={navigation} />);
    let input = findByTestAttr(wrapper, "login-input");
    let button = findByTestAttr(wrapper, "login-button");

    test('should not navigate to login mail screen if email adress is not entered', () => {
      input.simulate("changeText", "any@email.com");
      button.simulate("press");

      expect(navigation.navigate).toHaveBeenCalledTimes(1);

      //input.simulate("changeText", "");
      //button.simulate("press");
      //expect(navigation.navigate).toHaveBeenCalledTimes(0);
    });
});
Oğuzhan Atalay
  • 160
  • 2
  • 14

2 Answers2

0

Hello setValue is an asynchronous function so it doesn't update the state immediately, you can see the next value until the next rendering

add a console.log like below

import React, { useState } from "react";
import { View, Button, TextInput } from "react-native";

export const LoginContainer = (props) => {
  const [value, setValue] = useState("");

  const handleClick = () => {
    if (value !== "") {
      props.navigation.navigate("any other screen");
    }
  }
 // try to add console.log here and you will see the new value after click
 console.log("STATE_TEXT:", value);

  return (
    <View>
      <TextInput
        test-id="login-input"
        onChangeText={(text) => {
          console.log("INPUT_TEXT:", text);
          setValue(text);
        }} 
      // if you want the setValue to be executed immediately you can call it inside promise like below, however the value will still the same even after re-rendering
        //onChangeText={(text) => {
          //console.log("INPUT_TEXT:", text);
          //Promise.resolve().then(() => {
            //setValue(text);
            //console.log("STATE_TEXT after calling it inside promise:", value);
          //});
          //console.log("STATE_TEXT:", value);
        //});
        value={value}
      />
      <Button test-id="login-button" onPress={() => handleClick()} />
    </View>
  );
}

By default react re-render the component at the end of a react event handler. For more information about how react update state please check this blog here

ARZMI Imad
  • 950
  • 5
  • 8
  • I already know how it works, unfortunately this is not a solution for me. I'm just looking for a solution similar to `await` or `Promise` to wait and render like that – Oğuzhan Atalay Feb 14 '21 at 20:03
  • I did an update, so to conclude you can force setValue to be executed however the value inside the event handler will not change. – ARZMI Imad Feb 14 '21 at 20:51
  • I just solved this issue @ARZMI Imad and your information may be helper but not a right solution for this type of problem. Please check my answer. It's just needed to be updated in testing file. – Oğuzhan Atalay Feb 14 '21 at 20:59
  • welcome, but sorry to say that you're question were not clear, I just realize it from you're anwser. – ARZMI Imad Feb 14 '21 at 21:02
0

I solved this ussie with the solution in here.

test('should not navigate to login mail screen if email adress is not entered', () => {
    const wrapper = shallow(<LoginContainer navigation={navigation} />);
    input = findByTestAttr(wrapper, "login-input");

    input.simulate("changeText", "any@email.com");
    wrapper.update(); // update after changing the state
   
    // After performing update, should find button element
    button = findByTestAttr(wrapper, "login-button");
    button.simulate("press");
    expect(navigation.navigate).toHaveBeenCalledTimes(1);
});
Oğuzhan Atalay
  • 160
  • 2
  • 14