I want to create a JavaScript Github action and use Jest for testing purposes. Based on the docs I started parsing the input, given the following example code
import { getInput } from '@actions/core'; const myActionInput = getInput('my-key', { required: true });
Running this code during development throws the following error
Input required and not supplied: my-key
as expected because the code is not running inside a Github action environment. But is it possible to create tests for that? E.g.
describe('getMyKey', () => { it('throws if the input is not present.', () => { expect(() => getMyKey()).toThrow(); }); });
How can I “fake” / mock such an environment with a context to ensure my code works as expected?
Advertisement
Answer
There are several approaches you can take.
Set Inputs Manually
Inputs are passed to actions as environment variables with the prefix INPUT_
and uppercased. Knowing this, you can just set the respective environment variable before running the test.
In your case, the input my-key
would need to be present as the environment variable named INPUT_MY-KEY
.
This should make your code work:
describe('getMyKey', () => { it('throws if the input is not present.', () => { process.env['INPUT_MY-KEY'] = 'my-value'; expect(() => getMyKey()).toThrow(); }); });
Use Jest’s Mocking
You could use jest.mock
or jest.spyOn
and thereby mock the behaviour of getInput
.
Docs: ES6 Class Mocks
Abstract Action
I don’t like setting global environment variables, because one test might affect another depending on the order they are run in.
Also, I don’t like mocking using jest.mock
, because it feels like a lot of magic and I usually spend too much time getting it to do what I want. Issues are difficult to diagnose.
What seems to bring all the benefits with a tiny bit more code is to split off the action into a function that can be called by passing in the “global” objects like core
.
// index.js import core from '@actions/core'; action(core);
// action.js function action(core) { const myActionInput = core.getInput('my-key', { required: true }); }
This allows you to nicely test your action like so:
// action.js describe('getMyKey', () => { it('gets required key from input', () => { const core = { getInput: jest.fn().mockReturnValueOnce('my-value') }; action(core); expect(core.getInput).toHaveBeenCalledWith('my-key', { required: true }); }); });
Now you could say that we’re no longer testing if the action throws an error if the input is not present, but also consider what you’re really testing there: You’re testing if the core action throws an error if the input is missing. In my opinion, this is not your own code and therefore worthy of testing. All you want to make sure is that you’re calling the getInput
function correctly according to the contract (i.e. docs).