2
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.