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.










share|improve this question




























    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.










    share|improve this question


























      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.










      share|improve this question















      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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 1 hour ago









      Jamal

      30.2k11115226




      30.2k11115226










      asked 12 hours ago









      dwjohnston

      671516




      671516






















          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






          share|improve this answer





















          • 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











          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
          });


          }
          });














          draft saved

          draft discarded


















          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






          share|improve this answer





















          • 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















          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






          share|improve this answer





















          • 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













          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






          share|improve this answer













          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







          share|improve this answer












          share|improve this answer



          share|improve this answer










          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


















          • 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


















          draft saved

          draft discarded




















































          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          Quarter-circle Tiles

          build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

          Mont Emei