This technique for functional programming, and testing functional programming
up vote
2
down vote
favorite
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.
javascript unit-testing functional-programming
add a comment |
up vote
2
down vote
favorite
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.
javascript unit-testing functional-programming
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
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.
javascript unit-testing functional-programming
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.
javascript unit-testing functional-programming
javascript unit-testing functional-programming
edited 1 hour ago
Jamal♦
30.2k11115226
30.2k11115226
asked 12 hours ago
dwjohnston
671516
671516
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
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>
plnkr
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209718%2fthis-technique-for-functional-programming-and-testing-functional-programming%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
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>
plnkr
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
add a comment |
up vote
0
down vote
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>
plnkr
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
add a comment |
up vote
0
down vote
up vote
0
down vote
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>
plnkr
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>
plnkr
answered 5 hours ago
guest271314
21915
21915
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
add a comment |
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
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.
– dwjohnston
1 hour ago
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.
– dwjohnston
1 hour ago
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?
– guest271314
49 mins ago
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?
– guest271314
49 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
Alright - there's some fantastic resources there, thanks.
– dwjohnston
45 mins ago
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209718%2fthis-technique-for-functional-programming-and-testing-functional-programming%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown