|
1 | 1 | /**
|
2 | 2 | * Unit tests for the action's main functionality, src/main.ts
|
3 | 3 | *
|
4 |
| - * These should be run as if the action was called from a workflow. |
5 |
| - * Specifically, the inputs listed in `action.yml` should be set as environment |
6 |
| - * variables following the pattern `INPUT_<INPUT_NAME>`. |
| 4 | + * To mock dependencies in ESM, you can create fixtures that export mock |
| 5 | + * functions and objects. For example, the core module is mocked in this test, |
| 6 | + * so that the actual '@actions/core' module is not imported. |
7 | 7 | */
|
| 8 | +import { jest } from '@jest/globals' |
| 9 | +import * as core from '../__fixtures__/core.js' |
| 10 | +import { wait } from '../__fixtures__/wait.js' |
8 | 11 |
|
9 |
| -import * as core from '@actions/core' |
10 |
| -import * as main from '../src/main' |
| 12 | +// Mocks should be declared before the module being tested is imported. |
| 13 | +jest.unstable_mockModule('@actions/core', () => core) |
| 14 | +jest.unstable_mockModule('../src/wait.js', () => ({ wait })) |
11 | 15 |
|
12 |
| -// Mock the action's main function |
13 |
| -const runMock = jest.spyOn(main, 'run') |
| 16 | +// The module being tested should be imported dynamically. This ensures that the |
| 17 | +// mocks are used in place of any actual dependencies. |
| 18 | +const { run } = await import('../src/main.js') |
14 | 19 |
|
15 |
| -// Other utilities |
16 |
| -const timeRegex = /^\d{2}:\d{2}:\d{2}/ |
17 |
| - |
18 |
| -// Mock the GitHub Actions core library |
19 |
| -let debugMock: jest.SpiedFunction<typeof core.debug> |
20 |
| -let errorMock: jest.SpiedFunction<typeof core.error> |
21 |
| -let getInputMock: jest.SpiedFunction<typeof core.getInput> |
22 |
| -let setFailedMock: jest.SpiedFunction<typeof core.setFailed> |
23 |
| -let setOutputMock: jest.SpiedFunction<typeof core.setOutput> |
24 |
| - |
25 |
| -describe('action', () => { |
| 20 | +describe('main.ts', () => { |
26 | 21 | beforeEach(() => {
|
27 |
| - jest.clearAllMocks() |
| 22 | + // Set the action's inputs as return values from core.getInput(). |
| 23 | + core.getInput.mockImplementation(() => '500') |
28 | 24 |
|
29 |
| - debugMock = jest.spyOn(core, 'debug').mockImplementation() |
30 |
| - errorMock = jest.spyOn(core, 'error').mockImplementation() |
31 |
| - getInputMock = jest.spyOn(core, 'getInput').mockImplementation() |
32 |
| - setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation() |
33 |
| - setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation() |
| 25 | + // Mock the wait function so that it does not actually wait. |
| 26 | + wait.mockImplementation(() => Promise.resolve('done!')) |
34 | 27 | })
|
35 | 28 |
|
36 |
| - it('sets the time output', async () => { |
37 |
| - // Set the action's inputs as return values from core.getInput() |
38 |
| - getInputMock.mockImplementation(name => { |
39 |
| - switch (name) { |
40 |
| - case 'milliseconds': |
41 |
| - return '500' |
42 |
| - default: |
43 |
| - return '' |
44 |
| - } |
45 |
| - }) |
| 29 | + afterEach(() => { |
| 30 | + jest.resetAllMocks() |
| 31 | + }) |
46 | 32 |
|
47 |
| - await main.run() |
48 |
| - expect(runMock).toHaveReturned() |
| 33 | + it('Sets the time output', async () => { |
| 34 | + await run() |
49 | 35 |
|
50 |
| - // Verify that all of the core library functions were called correctly |
51 |
| - expect(debugMock).toHaveBeenNthCalledWith(1, 'Waiting 500 milliseconds ...') |
52 |
| - expect(debugMock).toHaveBeenNthCalledWith( |
53 |
| - 2, |
54 |
| - expect.stringMatching(timeRegex) |
55 |
| - ) |
56 |
| - expect(debugMock).toHaveBeenNthCalledWith( |
57 |
| - 3, |
58 |
| - expect.stringMatching(timeRegex) |
59 |
| - ) |
60 |
| - expect(setOutputMock).toHaveBeenNthCalledWith( |
| 36 | + // Verify the time output was set. |
| 37 | + expect(core.setOutput).toHaveBeenNthCalledWith( |
61 | 38 | 1,
|
62 | 39 | 'time',
|
63 |
| - expect.stringMatching(timeRegex) |
| 40 | + // Simple regex to match a time string in the format HH:MM:SS. |
| 41 | + expect.stringMatching(/^\d{2}:\d{2}:\d{2}/) |
64 | 42 | )
|
65 |
| - expect(errorMock).not.toHaveBeenCalled() |
66 | 43 | })
|
67 | 44 |
|
68 |
| - it('sets a failed status', async () => { |
69 |
| - // Set the action's inputs as return values from core.getInput() |
70 |
| - getInputMock.mockImplementation(name => { |
71 |
| - switch (name) { |
72 |
| - case 'milliseconds': |
73 |
| - return 'this is not a number' |
74 |
| - default: |
75 |
| - return '' |
76 |
| - } |
77 |
| - }) |
| 45 | + it('Sets a failed status', async () => { |
| 46 | + // Clear the getInput mock and return an invalid value. |
| 47 | + core.getInput.mockClear().mockReturnValueOnce('this is not a number') |
| 48 | + |
| 49 | + // Clear the wait mock and return a rejected promise. |
| 50 | + wait |
| 51 | + .mockClear() |
| 52 | + .mockRejectedValueOnce(new Error('milliseconds is not a number')) |
78 | 53 |
|
79 |
| - await main.run() |
80 |
| - expect(runMock).toHaveReturned() |
| 54 | + await run() |
81 | 55 |
|
82 |
| - // Verify that all of the core library functions were called correctly |
83 |
| - expect(setFailedMock).toHaveBeenNthCalledWith( |
| 56 | + // Verify that the action was marked as failed. |
| 57 | + expect(core.setFailed).toHaveBeenNthCalledWith( |
84 | 58 | 1,
|
85 |
| - 'milliseconds not a number' |
| 59 | + 'milliseconds is not a number' |
86 | 60 | )
|
87 |
| - expect(errorMock).not.toHaveBeenCalled() |
88 | 61 | })
|
89 | 62 | })
|
0 commit comments