简体   繁体   中英

Waiting for two separate promises to resolve so state is updated

So I'm testing a react component button click that changes a state. When rendering the component I need a promise to resolve so the button is rendered and thus clickable. I accomplish this by placing the button click inside of a setTimeout as the component states get updated. However after clicking the button a component state needs to be updated due to a promise being resolved. I'll give an example below

class App extends component {
     constructor(props){
     this.state ={
         hasError = false;
         loading = true;
   }
}

componentdidMount(){
    this.apiGetFunc();

apiGetFunc(){
  this.setState({hasError: false});
  this.setState({loading = false});
}

onClickFunc{
   this.middleWareCalltoAPI.then(
      respone =>{ this.setState({hasError: false})},
      errorRespone =>{ this.setState({hasError = true})};
   )
}


renderer(){
   return (
     <Card>
     {!this.state.hasError && !this.state.loading && (
     <div>
     <Button>
        onClick = {this.onClickfunc}
     </Button>
     </div>
     </Card>
     )}
   )
}

Now here is what my test looks like

test("Save user permissions", done => {
  mock.onGet("anAPI.php").reply(200, mockData); //THIS IS NEEDED TO RENDER THE BUTTON
  const wrapper = shallow(<App />);

  setTimeout(() => {
    wrapper.find("Button").simulate("click"); //THIS CLICK SHOULD CHANGE hasError to true
    expect(wrapper.state.hasError).toEqual(true) //THIS FAILS
    done();
  }, 0);
});

I've tried nesting setTimeouts so the promise from the click can resolve but that doesn't seem to work. I tried to pare down my code as much as possible so it's readable. Let me know if you need me to provide more context.

EDIT: Made the code more closely resemble what I actually have

There are many mistakes in your example code above that I'd highly suggest you invest some time in doing some simple React tutorials before attempting to move forward.

Nevertheless, here's a working example...

Working example : https://codesandbox.io/s/xj53m8lwvz (you can run the test by clicking the Tests tab near the bottom-left of the screen)

api/fakeAPI.js

const data = [
  {
    userId: 1,
    id: 1,
    title: "delectus aut autem",
    completed: false
  },
  {
    userId: 1,
    id: 2,
    title: "quis ut nam facilis et officia qui",
    completed: false
  },
  {
    userId: 1,
    id: 3,
    title: "fugiat veniam minus",
    completed: false
  },
  {
    userId: 1,
    id: 4,
    title: "et porro tempora",
    completed: true
  },
  {
    userId: 1,
    id: 5,
    title: "laboriosam mollitia et enim quasi adipisci quia provident illum",
    completed: false
  }
];

export const fakeAPI = {
  failure: () =>
    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("No data was found!");
      }, 1000);
    }),
  success: () =>
    new Promise(resolve => {
      setTimeout(() => {
        resolve(data);
      }, 1000);
    })
};

components/App/App.js

import React, { Component } from "react";
import ShowData from "../ShowData/showData";
import ShowError from "../ShowError/showError";
import { fakeAPI } from "../../api/fakeAPI";

export default class App extends Component {
  state = {
    data: [],
    hasError: "",
    isLoading: true
  };

  componentDidMount = () => {
    this.fetchData();
  };

  fetchData = () => {
    fakeAPI
      .success()
      .then(data => this.setState({ isLoading: false, data: data }))
      .catch(err => this.setState({ isLoading: false, hasError: err }));
  };

  handleClick = () => {
    this.setState({ isLoading: true, data: [] }, () => {
      fakeAPI
        .failure()
        .then(res => this.setState({ isLoading: false, hasError: "" }))
        .catch(err => this.setState({ isLoading: false, hasError: err }));
    });
  };

  render = () => (
    <div className="app-container">
      {this.state.isLoading ? (
         <ShowLoading />
      ) : this.state.hasError ? (
        <ShowError error={this.state.hasError} />
      ) : (
        <ShowData data={this.state.data} handleClick={this.handleClick} />
      )}
    </div>
  );
}

components/App/__test__/App.test.js ( mountWrap is a custom function you can find in test/utils/index.js and WaitForExpect is an easier way to wait for an assertion to be true within jest's default 5 second timeout)

import React from "react";
import { mountWrap } from "../../../test/utils";
import WaitForExpect from "wait-for-expect";
import App from "../App";

const initialState = {
  data: [],
  hasError: "",
  isLoading: true
};

const wrapper = mountWrap(<App />, initialState);
describe("App", () => {
  it("renders without errors", () => {
    expect(wrapper.find("div.app-container")).toHaveLength(1);
  });

  it("initally shows that it's loading", () => {
    expect(wrapper.state("isLoading")).toBeTruthy();
    expect(wrapper.find("div.loading")).toHaveLength(1);
  });

  it("renders data and shows an Update button", async () => {
    await WaitForExpect(() => {
      wrapper.update();
      expect(wrapper.state("isLoading")).toBeFalsy();
      expect(wrapper.find("div.data")).toHaveLength(5);
      expect(wrapper.find("button.update")).toHaveLength(1);
    });
  });

  it("shows an error once the button has been clicked", async () => {
    wrapper.find(".update").simulate("click");
    await WaitForExpect(() => {
      wrapper.update();
      expect(wrapper.state("isLoading")).toBeFalsy();
      expect(wrapper.state("hasError")).toBe("No data was found!");
      expect(wrapper.find("div.error")).toHaveLength(1);
    });
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM