Javascript 数组在使用酶渲染的组件中未定义?

Javascript 数组在使用酶渲染的组件中未定义?,javascript,reactjs,jestjs,react-hooks,enzyme,Javascript,Reactjs,Jestjs,React Hooks,Enzyme,我有一个简单的组件,它使用useSelector和useDispatch钩子从我的redux存储中分派和获取当前状态,并映射它们以返回一个子组件 我不熟悉使用钩子进行测试,在阅读了一些文章之后,我用jest mock模拟了两个redux钩子,以查看它们在装载时是否被调用,但是,在运行测试时,我的状态数组返回为未定义,因此测试失败。有人知道为什么会这样吗 COMPONENT.tsx import React, { useEffect, useState } from 'react'; import

我有一个简单的组件,它使用useSelector和useDispatch钩子从我的redux存储中分派和获取当前状态,并映射它们以返回一个子组件

我不熟悉使用钩子进行测试,在阅读了一些文章之后,我用jest mock模拟了两个redux钩子,以查看它们在装载时是否被调用,但是,在运行测试时,我的状态数组返回为未定义,因此测试失败。有人知道为什么会这样吗

COMPONENT.tsx

import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getData } from '../actions/index';
import { AppState } from '../store/store';

import GridItem from '../component/GridItem';

function GridContainer () {        
    const dispatch = useDispatch();
    const books = useSelector((state: AppState) => state);

    useEffect(() => {
        dispatch(getData());
    }, [dispatch]);
   
    const bookList = (list: AppState) => {
        return list.map((book) => (
            <GridItem key={book.id} data={ book } />
        ))
    };

    return (
        <div className="book-container">
            { bookList(books) }
        </div>
    )
}

export default GridContainer;
import React,{useffect,useState}来自“React”;
从'react redux'导入{useDispatch,useSelector};
从“../actions/index”导入{getData};
从“../store/store”导入{AppState};
从“../component/GridItem”导入GridItem;
函数GridContainer(){
const dispatch=usedpatch();
const books=useSelector((状态:AppState)=>state);
useffect(()=>{
调度(getData());
},[发送];
常量bookList=(列表:AppState)=>{
返回列表。地图((书本)=>(
))
};
返回(
{图书目录(图书)}
)
}
导出默认网格容器;
COMPONENT.test.tsx

import React from 'react';
import { shallow, mount } from 'enzyme';

import GridContainer from './GridContainer';

const mockDispatch = jest.fn();

jest.mock("react-redux", () => ({
    useSelector: jest.fn(fn => fn()),
    useDispatch: () => mockDispatch
}));

it('renders container', () => {
    const wrapper = shallow(<GridContainer />);
    const component = wrapper.find('.book-container');

    expect(component.length).toBe(1);
});

describe('react hooks', () => {
    afterEach(() => {
        jest.clearAllMocks();
    });

    it('runs dispatch on mount', () => {
        const wrapper = mount(<GridContainer />);

        expect(mockDispatch).toHaveBeenCalledTimes(1);
    });
});
从“React”导入React;
从“酶”导入{shall,mount};
从“./GridContainer”导入GridContainer;
const mockDispatch=jest.fn();
mock(“react redux”,()=>({
useSelector:jest.fn(fn=>fn()),
useDispatch:()=>mockDispatch
}));
它('呈现容器',()=>{
常量包装器=浅();
const component=wrapper.find('.book container');
expect(组件长度).toBe(1);
});
描述('react hooks',()=>{
之后(()=>{
开玩笑。clearAllMocks();
});
它('在装载时运行分派',()=>{
const wrapper=mount();
预计(模拟调度)。已催收时间(1);
});
});
错误消息

 TypeError: Cannot read property 'map' of undefined

      25 |     //function to print list of books component
      26 |     const bookList = (list: AppState) => {
    > 27 |         return list.map((book) => (
         |                     ^
      28 |             <GridItem key={book.id} data={ book } />
      29 |         ))
      30 |     };
TypeError:无法读取未定义的属性“map”
25 |//打印图书列表组件的函数
26 | const bookList=(列表:AppState)=>{
>27 |返回列表。地图((书)=>(
|                     ^
28 |             
29 |         ))
30 |     };

问题在于模仿
useSelector
。函数本身将
AppState
传递给回调,但模拟函数不知道
AppState
。这样,您的模拟将返回未定义。这样
书籍
(列表)将
未定义

您需要指定模拟将返回的内容,在本例中是一个数组。您可以调用
mockReturnValue
传递booksMock(数组)来实现:

jest.mock("react-redux", () => ({
    useSelector: jest.fn().mockReturnValue(booksMock),
    useDispatch: () => mockDispatch
}));

问题在于模拟
useSelector
。函数本身将
AppState
传递给回调,但模拟函数不知道
AppState
。这样,您的模拟将返回未定义。这样
书籍
(列表)将
未定义

您需要指定模拟将返回的内容,在本例中是一个数组。您可以调用
mockReturnValue
传递booksMock(数组)来实现:

jest.mock("react-redux", () => ({
    useSelector: jest.fn().mockReturnValue(booksMock),
    useDispatch: () => mockDispatch
}));

您应该模拟
useSelector
hook的返回值

例如

GridContainer.tsx

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getData } from './actions/index';
import { AppState } from './store/store';
import GridItem from './component/GridItem';

function GridContainer() {
  const dispatch = useDispatch();
  const books = useSelector((state: AppState) => state);
  console.log('books:', books);

  useEffect(() => {
    dispatch(getData());
  }, [dispatch]);

  const bookList = (list: AppState) => {
    return list.map((book) => <GridItem key={book.id} data={book} />);
  };

  return <div className="book-container">{bookList(books)}</div>;
}

export default GridContainer;
import React from 'react';

export default function GridItem({ data }) {
  return <div>{data.name}</div>;
}
import React from 'react';
import { shallow, mount } from 'enzyme';
import GridContainer from './GridContainer';
import { useDispatch, useSelector } from 'react-redux';

const mockDispatch = jest.fn();
const booksMock = [
  { id: 1, name: 'golang' },
  { id: 2, name: 'TypeScript' },
];

jest.mock('react-redux', () => {
  return {
    useSelector: jest.fn(() => booksMock),
    useDispatch: jest.fn(() => mockDispatch),
  };
});

describe('react hooks', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('renders container', () => {
    const wrapper = shallow(<GridContainer />);
    const component = wrapper.find('.book-container');
    expect(component.length).toBe(1);
  });

  it('runs dispatch on mount', () => {
    const wrapper = mount(<GridContainer />);
    expect(useDispatch).toBeCalledTimes(1);
    expect(useSelector).toBeCalledWith(expect.any(Function));
    expect(mockDispatch).toBeCalledWith({ type: 'GET_DATA' });
    expect(wrapper.find('.book-container').children()).toHaveLength(2);
  });
});
/store/store.ts

export type AppState = any[];
export function getData() {
  return { type: 'GET_DATA' };
}
/actions/index.ts

export type AppState = any[];
export function getData() {
  return { type: 'GET_DATA' };
}
GridContainer.test.tsx

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getData } from './actions/index';
import { AppState } from './store/store';
import GridItem from './component/GridItem';

function GridContainer() {
  const dispatch = useDispatch();
  const books = useSelector((state: AppState) => state);
  console.log('books:', books);

  useEffect(() => {
    dispatch(getData());
  }, [dispatch]);

  const bookList = (list: AppState) => {
    return list.map((book) => <GridItem key={book.id} data={book} />);
  };

  return <div className="book-container">{bookList(books)}</div>;
}

export default GridContainer;
import React from 'react';

export default function GridItem({ data }) {
  return <div>{data.name}</div>;
}
import React from 'react';
import { shallow, mount } from 'enzyme';
import GridContainer from './GridContainer';
import { useDispatch, useSelector } from 'react-redux';

const mockDispatch = jest.fn();
const booksMock = [
  { id: 1, name: 'golang' },
  { id: 2, name: 'TypeScript' },
];

jest.mock('react-redux', () => {
  return {
    useSelector: jest.fn(() => booksMock),
    useDispatch: jest.fn(() => mockDispatch),
  };
});

describe('react hooks', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('renders container', () => {
    const wrapper = shallow(<GridContainer />);
    const component = wrapper.find('.book-container');
    expect(component.length).toBe(1);
  });

  it('runs dispatch on mount', () => {
    const wrapper = mount(<GridContainer />);
    expect(useDispatch).toBeCalledTimes(1);
    expect(useSelector).toBeCalledWith(expect.any(Function));
    expect(mockDispatch).toBeCalledWith({ type: 'GET_DATA' });
    expect(wrapper.find('.book-container').children()).toHaveLength(2);
  });
});

源代码:

您应该模拟
useSelector
hook的返回值

例如

GridContainer.tsx

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getData } from './actions/index';
import { AppState } from './store/store';
import GridItem from './component/GridItem';

function GridContainer() {
  const dispatch = useDispatch();
  const books = useSelector((state: AppState) => state);
  console.log('books:', books);

  useEffect(() => {
    dispatch(getData());
  }, [dispatch]);

  const bookList = (list: AppState) => {
    return list.map((book) => <GridItem key={book.id} data={book} />);
  };

  return <div className="book-container">{bookList(books)}</div>;
}

export default GridContainer;
import React from 'react';

export default function GridItem({ data }) {
  return <div>{data.name}</div>;
}
import React from 'react';
import { shallow, mount } from 'enzyme';
import GridContainer from './GridContainer';
import { useDispatch, useSelector } from 'react-redux';

const mockDispatch = jest.fn();
const booksMock = [
  { id: 1, name: 'golang' },
  { id: 2, name: 'TypeScript' },
];

jest.mock('react-redux', () => {
  return {
    useSelector: jest.fn(() => booksMock),
    useDispatch: jest.fn(() => mockDispatch),
  };
});

describe('react hooks', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('renders container', () => {
    const wrapper = shallow(<GridContainer />);
    const component = wrapper.find('.book-container');
    expect(component.length).toBe(1);
  });

  it('runs dispatch on mount', () => {
    const wrapper = mount(<GridContainer />);
    expect(useDispatch).toBeCalledTimes(1);
    expect(useSelector).toBeCalledWith(expect.any(Function));
    expect(mockDispatch).toBeCalledWith({ type: 'GET_DATA' });
    expect(wrapper.find('.book-container').children()).toHaveLength(2);
  });
});
/store/store.ts

export type AppState = any[];
export function getData() {
  return { type: 'GET_DATA' };
}
/actions/index.ts

export type AppState = any[];
export function getData() {
  return { type: 'GET_DATA' };
}
GridContainer.test.tsx

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getData } from './actions/index';
import { AppState } from './store/store';
import GridItem from './component/GridItem';

function GridContainer() {
  const dispatch = useDispatch();
  const books = useSelector((state: AppState) => state);
  console.log('books:', books);

  useEffect(() => {
    dispatch(getData());
  }, [dispatch]);

  const bookList = (list: AppState) => {
    return list.map((book) => <GridItem key={book.id} data={book} />);
  };

  return <div className="book-container">{bookList(books)}</div>;
}

export default GridContainer;
import React from 'react';

export default function GridItem({ data }) {
  return <div>{data.name}</div>;
}
import React from 'react';
import { shallow, mount } from 'enzyme';
import GridContainer from './GridContainer';
import { useDispatch, useSelector } from 'react-redux';

const mockDispatch = jest.fn();
const booksMock = [
  { id: 1, name: 'golang' },
  { id: 2, name: 'TypeScript' },
];

jest.mock('react-redux', () => {
  return {
    useSelector: jest.fn(() => booksMock),
    useDispatch: jest.fn(() => mockDispatch),
  };
});

describe('react hooks', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('renders container', () => {
    const wrapper = shallow(<GridContainer />);
    const component = wrapper.find('.book-container');
    expect(component.length).toBe(1);
  });

  it('runs dispatch on mount', () => {
    const wrapper = mount(<GridContainer />);
    expect(useDispatch).toBeCalledTimes(1);
    expect(useSelector).toBeCalledWith(expect.any(Function));
    expect(mockDispatch).toBeCalledWith({ type: 'GET_DATA' });
    expect(wrapper.find('.book-container').children()).toHaveLength(2);
  });
});

源代码:

Hi,感谢您回来,我尝试了您的解决方案,但是错误是一样的,再看一看,似乎由于某种原因Ezyme没有检测到我的redux状态Hi,感谢您回来,我尝试了您的解决方案,但是错误是一样的,在看了多一点之后,似乎是因为某种原因,酶没有恢复我的redux状态