React Native + Enzyme + Jest:模拟的 redux 函数调用未注册/被调用零次
React Native + Enzyme + Jest: Mocked redux function call is not registered / is called zero times
我正在使用 React Native 和 Redux 编写应用程序。我正在设计一个登录表单并想测试组件处理提交功能。在 handleSubmit()
函数中,应该将几个动作分派给 Redux。让我为您提供 handleSubmit()
函数代码及其测试。首先是函数本身:
handleSubmit = (values, formikBag) => {
formikBag.setSubmitting(true);
const { loginSuccess, navigation, setHouses, setCitizens } = this.props;
apiLoginUser(values.email, values.password)
.then(data => {
const camelizedJson = camelizeKeys(data.user);
const normalizedData = Object.assign({}, normalize(camelizedJson, userSchema));
loginSuccess(normalizedData);
const tokenPromise = setToken(data.key);
const housePromise = getHouseList();
Promise.all([tokenPromise, housePromise])
.then(values => {
setHouses(values[1]);
getCitizenList(values[1].result[0])
.then(citizens => {
setCitizens(citizens);
formikBag.setSubmitting(false);
navigation.navigate("HomeScreen");
})
.catch(err => {
formikBag.setSubmitting(false);
alert(err);
});
})
.catch(err => {
console.log(err);
formikBag.setSubmitting(false);
alert(err);
});
})
.catch(error => {
alert(error);
formikBag.setSubmitting(false);
});
};
如您所见,我也在使用 normalizr 来解析数据。 getHouseList()
和 getCitizenList()
函数的数据在各自的函数中被归一化。
测试如下:
const createTestProps = props => ({
navigation: { navigate: jest.fn() },
loginSuccess: jest.fn(),
setHouses: jest.fn(),
setCitizens: jest.fn(),
...props
});
...
describe("component methods", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<LoginForm {...props} />);
fetch.mockResponseOnce(JSON.stringify(userResponse));
fetch.mockResponseOnce(JSON.stringify(housesResponse));
fetch.mockResponseOnce(JSON.stringify(citizensResponse));
wrapper
.instance()
.handleSubmit({ email: "abc", password: "def" }, { setSubmitting: jest.fn() });
});
afterEach(() => {
jest.clearAllMocks();
});
it("should dispatch a loginSuccess() action", () => {
expect(props.loginSuccess).toHaveBeenCalledTimes(1);
});
});
在此测试中,提供给 jest-fetch-mocks
(userResponse
、housesResponse
和 citizensResponse
)的值是常量。我现在这个测试失败了,因为显然从未调用应该调度 Redux 操作的 loginSuccess()
(即使我在 createProps()
函数中提供了 jest.fn()
)。
我做错了什么?为什么从未调用 loginSuccess()
函数?
编辑: 根据 Brian 的要求,这里是 api 调用的代码:
export const apiLoginUser = (email, password) =>
postRequestWithoutHeader(ROUTE_LOGIN, { email: email, password: password });
export const postRequestWithoutHeader = (fullUrlRoute, body) =>
fetch(fullUrlRoute, {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
}).then(response =>
response.json().then(json => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
);
问题
props.loginSuccess()
上的断言发生在调用它的代码具有 运行.
之前
详情
重要的是要记住 JavaScript 是单线程的并且在消息队列之外工作(参见 Concurrency model and Event Loop)。
它从队列中获取一条消息,运行发送相关函数直到堆栈为空,然后return发送到队列以获取下一条消息。
JavaScript 中的异步代码通过向队列添加消息来工作。
在这种情况下,在 apiLoginUser()
内对 then()
的调用正在将消息添加到队列中,但在 beforeEach()
和 it('should dispatch a loginSucces() action')
之间并不是所有的消息都有还有机会执行。
解决方案
解决方案是确保最终调用 loginSuccess()
的排队消息在执行断言之前都有机会 运行。
有两种可能的方法:
方法一
handleSubmit()
return apiLoginUser()
创建的承诺,然后 return beforeEach()
末尾的承诺。从 beforeEach()
返回 Promise
将导致 Jest
to wait for it to resolve before running the test.
方法二
等待 Promise
是理想的,但如果无法更改代码,则可以手动将测试中的断言延迟所需的事件循环周期数。最干净的方法是使测试异步并等待已解决的Promise
(如果需要多个事件循环周期,则等待一系列承诺):
it('should dispatch a loginSuccess() action', async () => {
// queue the rest of the test so any pending messages in the queue can run first
await Promise.resolve();
expect(props.loginSuccess).toHaveBeenCalledTimes(1);
});
我正在使用 React Native 和 Redux 编写应用程序。我正在设计一个登录表单并想测试组件处理提交功能。在 handleSubmit()
函数中,应该将几个动作分派给 Redux。让我为您提供 handleSubmit()
函数代码及其测试。首先是函数本身:
handleSubmit = (values, formikBag) => {
formikBag.setSubmitting(true);
const { loginSuccess, navigation, setHouses, setCitizens } = this.props;
apiLoginUser(values.email, values.password)
.then(data => {
const camelizedJson = camelizeKeys(data.user);
const normalizedData = Object.assign({}, normalize(camelizedJson, userSchema));
loginSuccess(normalizedData);
const tokenPromise = setToken(data.key);
const housePromise = getHouseList();
Promise.all([tokenPromise, housePromise])
.then(values => {
setHouses(values[1]);
getCitizenList(values[1].result[0])
.then(citizens => {
setCitizens(citizens);
formikBag.setSubmitting(false);
navigation.navigate("HomeScreen");
})
.catch(err => {
formikBag.setSubmitting(false);
alert(err);
});
})
.catch(err => {
console.log(err);
formikBag.setSubmitting(false);
alert(err);
});
})
.catch(error => {
alert(error);
formikBag.setSubmitting(false);
});
};
如您所见,我也在使用 normalizr 来解析数据。 getHouseList()
和 getCitizenList()
函数的数据在各自的函数中被归一化。
测试如下:
const createTestProps = props => ({
navigation: { navigate: jest.fn() },
loginSuccess: jest.fn(),
setHouses: jest.fn(),
setCitizens: jest.fn(),
...props
});
...
describe("component methods", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<LoginForm {...props} />);
fetch.mockResponseOnce(JSON.stringify(userResponse));
fetch.mockResponseOnce(JSON.stringify(housesResponse));
fetch.mockResponseOnce(JSON.stringify(citizensResponse));
wrapper
.instance()
.handleSubmit({ email: "abc", password: "def" }, { setSubmitting: jest.fn() });
});
afterEach(() => {
jest.clearAllMocks();
});
it("should dispatch a loginSuccess() action", () => {
expect(props.loginSuccess).toHaveBeenCalledTimes(1);
});
});
在此测试中,提供给 jest-fetch-mocks
(userResponse
、housesResponse
和 citizensResponse
)的值是常量。我现在这个测试失败了,因为显然从未调用应该调度 Redux 操作的 loginSuccess()
(即使我在 createProps()
函数中提供了 jest.fn()
)。
我做错了什么?为什么从未调用 loginSuccess()
函数?
编辑: 根据 Brian 的要求,这里是 api 调用的代码:
export const apiLoginUser = (email, password) =>
postRequestWithoutHeader(ROUTE_LOGIN, { email: email, password: password });
export const postRequestWithoutHeader = (fullUrlRoute, body) =>
fetch(fullUrlRoute, {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
}).then(response =>
response.json().then(json => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
);
问题
props.loginSuccess()
上的断言发生在调用它的代码具有 运行.
详情
重要的是要记住 JavaScript 是单线程的并且在消息队列之外工作(参见 Concurrency model and Event Loop)。
它从队列中获取一条消息,运行发送相关函数直到堆栈为空,然后return发送到队列以获取下一条消息。
JavaScript 中的异步代码通过向队列添加消息来工作。
在这种情况下,在 apiLoginUser()
内对 then()
的调用正在将消息添加到队列中,但在 beforeEach()
和 it('should dispatch a loginSucces() action')
之间并不是所有的消息都有还有机会执行。
解决方案
解决方案是确保最终调用 loginSuccess()
的排队消息在执行断言之前都有机会 运行。
有两种可能的方法:
方法一
handleSubmit()
return apiLoginUser()
创建的承诺,然后 return beforeEach()
末尾的承诺。从 beforeEach()
返回 Promise
将导致 Jest
to wait for it to resolve before running the test.
方法二
等待 Promise
是理想的,但如果无法更改代码,则可以手动将测试中的断言延迟所需的事件循环周期数。最干净的方法是使测试异步并等待已解决的Promise
(如果需要多个事件循环周期,则等待一系列承诺):
it('should dispatch a loginSuccess() action', async () => {
// queue the rest of the test so any pending messages in the queue can run first
await Promise.resolve();
expect(props.loginSuccess).toHaveBeenCalledTimes(1);
});