Article Cover of Testing Non-Exported Functions in JavaScript

# Testing Non-Exported Functions in JavaScript

Recently, I finally integrated unit testing into my startup project. I've settled with Jest, I'll speak more about this in a separate journal entry. While writing my test, I ran into a bit of a dilemma of trying to write unit tests for non-exported functions 😖

# Testing Exported Function

It's super straightforward to test exported functions.

// utils.js
export function sayHi() {
  return '👋';
}

And a unit test could be something like this:

// utils.test.js

import { sayHi } from './utils.js';

describe('sayHi', () => {
  it('returns wave emoji', () => {
    expect(sayHi()).toBe('👋');
  });
});

# Non-export function

Now, what if the function is not exported?

function saySecret() {
  return '🤫';
}

Ah yikes, there is no way to test it! 🤷‍♀️

// utils.test.js

// ❌
import { saySecret } from './utils.js';

saySecret; // undefined

# Introducing Rewire

And then I discover this nifty package called Rewire! Here's their official description:

Rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may

  • inject mocks for other modules or globals like process
  • inspect private variables
  • override variables within the module.

The second point is exactly what I needed!

# Installing Rewire for a Vue app

Instead of using rewire, I used a package called babel-plugin-rewire. Which is essentially ES6 version of rewire, so I can use import. Here's their description:

It is inspired by rewire.js and transfers its concepts to es6 using babel.

# Step 1: Install package

# Yarn
yarn add -D babel-plugin-rewire

# Npm
npm install babel-plugin-rewire --save-dev

# Step 2: Add to babel config

babel.config.js

module.exports = {
  plugins: ['babel-plugin-rewire'],
};

# Step 3: Using it

Alright, now that it's installed, let's revisit our non-exported function.

function saySecret() {
  return '🤫';
}

And now, we can use rewire to fetch our non-export function:

// utils.test.js

import utilsRewire from './utils.js';

describe('saySecret', () => {
  it('returns shh emoji', () => {
    const saySecret = utilsRewire.__get__('saySecret'); // 👈 the secret sauce

    expect(saySecret()).toBe('🤫');
  });
});

# Non-exported function must be called in Exported Function

One important thing I need to point out! In order to test the non-exported function, it needs to be used in an exported function.

❌ So this won't work on its own.

function saySecret() {
  return '🤫';
}

✅ You need to also call this in an exported function of the same file.

function sayHi(password) {
  if (password) {
    saySecret(); // 👈 Calling the non-export function
  }
}

Now, can you actually test it 👍

// utils.test.js

import utilsRewire from './utils.js';

describe('saySecret', () => {
  it('returns shh emoji', () => {
    const saySecret = utilsRewire.__get__('saySecret');

    expect(saySecret()).toBe('🤫');
  });
});

# Warning! Vuex with Rewire

To my dismay, after I finally got rewire set up and successfully added testing for my non-export functions. When I serve up my Vue app, I got this error:

❌ Uncaught Error: [vuex] actions should be function or object with "handler" function but "actions.default" in module "editor" is {}.

🤦‍♀️ Like many developers, when one hits a roadblock, you shut the project and give up! NO! That's not the developer way -- you go to LinkedIn and starting looking for a new career 😖 Again NO 😂 Let's see what Google has to say!

Often, I'll tell junior developers to just Google it. But even googling is a skill that takes time to hone. And knowing what to search is important. So I'm going to share the terms I used:

  • (copy & paste the error)
  • Rewire not working with Vuex

Luckily on the second search, I found the solution! Turns out GitLab had the same problem and even posted a solution. Let me copy and paste their findings:

[Rewire] adds a default export to any module which does not already have one. This causes problems with our current pattern of using import * as getters from './getters.js' for Vuex resources because default will end up being an unexpected data type (object, not function). As a result we've had to add export default function() {} to each of our getters to ensure this doesn't cause Vuex to complain.

Excellent, not only did they explain the problem, they provided the solution 👏

# 1. My Problematic Code

In my Vue app, I had the same pattern as GitLab. Not surprisingly, I work there so I just reference the same pattern from work 😅. This was my original setup:

// actions.js

export const someAction = () => {};
// store/index.js

import * as actions from './actions';

export default {
  actions,
};

# 2. The solution

Using the solution found from GitLab, all I had to do is add a default export like so:

// actions.js

export default function() {} // 👈 Add this!

export const someAction = () => {};

# Alternative solutions

Of course, I could avoid this default export by following a different pattern. On the official Vuex guide, they have a Shopping cart example you can reference. They have something like this:

// modules/cart.js

const actions = {
  someAction() {},
};

export default { // 👈 no problem cause there's the default!
  actions,
};
// store/index.js

import cart from './modules/cart';

export default new Vuex.Store({
  modules: {
    cart,
  },
});

# Proficiency leads to Result!

Maybe down the road, I'll change it, But that's what I have now so I'll just leave it 😅 In programming, I learned very early on, that there are always multiple solutions. There is often no best way, there's only the way that works for you 👍

I like my current setup. And to be honest, I'm more experienced with this way (heads up, I work at GitLab). So for me, this is MY best way. And when you're working on a startup, proficiency is key. You don't want to spend your time spinning your wheels to learn something. It's all about the RESULT. Pick the tool you're more familiar and start producing 💪

# Beginner Friendly Resources

If you come from my Tidbit community, you will be familiar with my more beginner-friendly posts. However, with my journal series, some of the topics will be a bit more advance. As they are topics that I'm encountering while I'm building up my startup project. I'm learning so much from it so I just want to keep knowledge sharing. And to able to churn these post out quickly, I often won't be able to lay out the foundation -- so I apologize in advance to the more beginner folks 😓 But don't fret! We all once started as beginners, as long as we put in the work, we can all level up! 🧗‍♀️

Here's what I'll do, I'll link up resources that might help you follow my entry a bit more. Thanks again for reading my journal and can't wait to share more!

# Unit testing in JavaScript Part 1 - Why unit testing?

# Jest Crash Course - Unit Testing in JavaScript

# Resources


Related Articles

  • #BlackLivesMatter

    #BlackLivesMatter

  • Testing Non-Exported Functions in JavaScript with Rewire

    Testing Non-Exported Functions in JavaScript with Rewire

  • Building my New Site with Tailwind CSS

    Building my New Site with Tailwind CSS

  • Building my New Site with VuePress

    Building my New Site with VuePress

  • My Top 3 Favorite FREE JavaScript Courses

    My Top 3 Favorite FREE JavaScript Courses