I've been playing around with functional programming and testing with jest recently.
An issue I've run into, is that it's hard to mock ES6 module exports directly. See this Stack Overflow question for more details.
The solution I've come up with is to inject the dependency functions into the function that is calling it, via .bind
.
Here's an example:
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
const URI_USERS = 'users/';
/**
* These are the implementation of our functions, which take functions as arguments.
*/
export const _fetchUsers = async function (_makeApiCall, _URI_USERS) {
return _makeApiCall(_URI_USERS);
}
export const _fetchUser = async function (_makeApiCall, _URI_USERS, id) {
return _makeApiCall(_URI_USERS + id);
}
export const _fetchUserStrings = async function (_fetchUser, _parseUser, ...ids) {
const users = await Promise.all(ids.map(id => _fetchUser(id)));
return users.map(user => _parseUser(user));
}
export const _makeApiCall = async function (uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw new Error(err.message);
}
}
export function _parseUser(user) {
return `${user.name}:${user.username}`;
}
/**
* Real exports
*/
export const makeApiCall = _makeApiCall;
export const parseUser = _parseUser;
export const fetchUsers = _fetchUsers.bind(null, _makeApiCall, URI_USERS);
export const fetchUser = _fetchUser.bind(null, _makeApiCall, URI_USERS);
export const fetchUserStrings = _fetchUserStrings.bind(null, _fetchUser, _parseUser);
And a test:
/**
* Our most complicated test
*
*/
describe("_fetchUserStrings", () => {
describe("happy flow", () => {
const fetchUserMock = jest.fn((i) => Promise.resolve({
username: "foo",
name: "bar"
}));
const parseUserMock = jest.fn(user => "string");
const fetchUserStrings = _fetchUserStrings.bind(null, fetchUserMock, parseUserMock);
it("returns an array of three strings", async () => {
expect.assertions(3);
const result = await fetchUserStrings(1, 2, 3);
// I'm being a bit lazy here, you could be checking that
// The strings are actually there etc, but whatevs.
expect(fetchUserMock).toHaveBeenCalledTimes(3);
expect(parseUserMock).toHaveBeenCalledTimes(3);
expect(result).toHaveLength(3);
})
});
});
My question is - is this a super clean way of writing functional JavaScript, or is this overkill that is easily solved another way?
If you're interested, there are more examples here.
1 Answer 1
or is this overkill that is easily solved another way?
_fetchUser
and _fetchUsers
appear to be the same function; save for the id
parameter.
If interpret the question correctly, _fetchUser
can be substituted for _fetchUsers
(which can be removed entirely) by utilizing default parameters instead of .bind()
.
For example
async function _makeApiCall (uri) {
try {
const response = await Promise.resolve(uri);
return response;
} catch (err) {
throw new Error(err.message);
}
}
async function _fetchUsers({makeApiCall = _makeApiCall, _URI_USERS = 'abc', id = ''} = {}) {
return await makeApiCall(_URI_USERS + id);
}
export default _fetchUsers;
<script type="module">
import _fetchUsers from './script.js';
(async() => {
console.log(await _fetchUsers() // 'abc'
, await _fetchUsers({id:'def'}) // 'abcdef'
);
})();
</script>
-
\$\begingroup\$ Re: deault parameters - this makes it difficult/impsible to use a rest operator (for passing in an array of arguments) - see this question: stackoverflow.com/questions/53752586/…, also - having the functions as parameters means they'll show up on the IDE and generally muddy it up. \$\endgroup\$dwjohnston– dwjohnston2018年12月15日 22:09:18 +00:00Commented Dec 15, 2018 at 22:09
-
\$\begingroup\$ Rest element and rest parameter are not an operators What is SpreadElement in ECMAScript documentation? Is it the same as Spread syntax at MDN? Concerning using both rest parameters and default parameters, are you trying to do something like arrow functions: destructuring arguments and grabbing them as a whole at the same time or Can we set persistent default parameters which remain set until explicitly changed?? How is an IDE related to the question? \$\endgroup\$guest271314– guest2713142018年12月15日 22:20:36 +00:00Commented Dec 15, 2018 at 22:20
-
\$\begingroup\$ Alright - there's some fantastic resources there, thanks. \$\endgroup\$dwjohnston– dwjohnston2018年12月15日 22:24:08 +00:00Commented Dec 15, 2018 at 22:24
Explore related questions
See similar questions with these tags.