Within Red Hat, we previously had been working on a complete refactor of a custom GitHub action called build-chain. During the course of this project, however, we realized that the only way to perform any kind of end-to-end testing for this custom action was by running it manually on GitHub. We would have had to make multiple test repositories and fake pull requests (PRs), and then manually verify the output upon running the action. Too much manual work!
We noticed that this issue wasn’t just limited to custom actions but also affected any kind of workflow. We ran into this annoying problem of having to test it by pushing it to GitHub, triggering the workflow, manually checking the output, making fixes and repeating this process until we got it right. At the end of this vicious cycle, we ended up with a polluted git history consisting of multiple useless commits and workflow runs.
Wouldn’t nektos/act cover this case?
The command line interface (CLI) tool nektos/act is used to run GitHub actions locally. So it might seem that this tool resolves our issues, but it lacks certain important features:
- There is no API for it (i.e. it is difficult to interact with this tool programmatically)
- There is no option to mock APIs during a workflow run
- There is no option to mock an entire step during a workflow run
- It runs the workflow using the actual repository, risking making actual changes to it (for example, running a workflow which pushes to a branch)
This led to the creation of mock-github and act-js—a one-stop solution for creating a local git environment to run our GitHub actions locally and programmatically.
Mock-github
Mock-github is a Node.JS library that allows you to configure and make completely local and functioning git repositories. In these repositories, you can add, commit and push files, create branches, merge branches, etc. Moreover, it provides an Octokit-like interface called Moctokit for mocking your GitHub API calls. For the sake of consistency, it also provides an interface called Mockapi to mock any APIs using the same interface that Moctokit uses (a use case for this will become more apparent later in this post).
Act-js
Act-js is a Node.JS wrapper for the nektos/act CLI tool, and provides a way to programmatically run your GitHub actions locally and verify their output. Additionally, you can mock any API call that’s made during the workflow run using Moctokit and Mockapi. As an alternative, you can also mock the entire step itself.
An example
Let’s see an example of how we can use these two libraries to test a workflow file programmatically.
Consider the following simple workflow file:
name: Test on: push jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: github api call run: | result=$(curl -s http://api.github.com/repos/owner/project) echo "$result" - name: custom api call run: | result=$(curl -s http://google.com) echo "$result"
It checks out the repository and makes two API calls—one to GitHub and one to Google.
Thanks to mock-github and Act-js, it’s now possible to test this workflow using Jest in a TypeScript environment. The first thing to do is create a local git repository in which we can run our GitHub action. Think of this as creating a clean standard environment to test your code in:
const github = new MockGithub({ repo: { foo: { files: [ { src: path.resolve(__dirname, "..", ".github"), dest: ".github", } ], }, }, }); await github.setup();
This creates a local repository called “foo”. Our workflow files are placed into the .github directory of the foo repository. In this case, only the workflow file is needed in our local repository, but you can copy over whichever file/directory you want to the local repository.
Now, we should also prepare to mock the API calls during our test workflow run. In this case, it might seem a bit silly to do so, but imagine a more complex workflow where it depends on the data these APIs sent back. If we do not mock these APIs, then the results of running the same test might differ from one run to another (for example, if the repository is deleted then the GitHub API will fail). Moreover, what if we have a strict API rate limit? We don’t want to waste them on testing. So, we will use Moctokit and Mockapi.
const moctokit = new Moctokit("http://api.github.com"); const mockapi = new Mockapi({ google: { baseUrl: "http://google.com", endpoints: { root: { get: { path: "/", method: "get", parameters: { query: [], path: [], body: [], }, }, }, }, }, });
Here we have initialized Moctokit to use “http://api.github.com” as our base URL, and we have initialized Mockapi with the schema of our custom API. We are now ready to execute our workflow locally using Act from the act-js library:
const act = new Act(github.repo.getPath("foo")); const result = await act.runEvent("push", { mockApi: [ moctokit.rest.repos .get({ owner: "owner", repo: "project" }) .setResponse({ status: 200, data: { full_name: "owner/project" } }), mockapi.mock.google.root .get() .setResponse({ status: 200, data: "mock response" }), ], });
First, we initialize Act to run only in the foo local repository. Next, we trigger the workflow using the push event and define two mocks for this workflow execution. The first one is used to mock the GitHub API. Whenever an API call is made to GitHub to get the details for the repository owner/project in the workflow, it will receive our mocked response back rather than actual data. The next mock is for the Google API, which also behaves the same way.
Finally, let's compare the result of the run with the expected output:
expect(result).toStrictEqual([ { name: "Main actions/checkout@v3", status: 0, output: "", }, { name: "Main github api call", status: 0, output: '{"full_name":"owner/project"}', }, { name: "Main custom api call", status: 0, output: "mock response", }, ]);
Our expected output consists of three steps (as seen in the workflow file). For the checkout step, we don’t expect any output. It should just successfully execute as indicated by status 0. The next two steps should print the data from our mocked response without failing. Notice that “Main” is added before each step. This indicates that these steps are not part of the pre or post section of the workflow.
Now, put all of this together in ci.test.ts file:
import { Act } from "@kie/act-js"; import { Mockapi, MockGithub, Moctokit } from "@kie/mock-github"; import path from "path"; let github: MockGithub; beforeEach(async () => { github = new MockGithub({ repo: { foo: { files: [ { src: path.resolve(__dirname, "..", ".github"), dest: ".github", }, ], }, }, }); await github.setup(); }); afterEach(async () => { await github.teardown(); }); test("api workflow", async () => { const moctokit = new Moctokit("http://api.github.com"); const mockapi = new Mockapi({ google: { baseUrl: "http://google.com", endpoints: { root: { get: { path: "/", method: "get", parameters: { query: [], path: [], body: [], }, }, }, }, }, }); const act = new Act(github.repo.getPath("foo")); const result = await act.runEvent("push", { mockApi: [ moctokit.rest.repos .get({ owner: "owner", repo: "project" }) .setResponse({ status: 200, data: { full_name: "owner/project" } }), mockapi.mock.google.root .get() .setResponse({ status: 200, data: "mock response" }), ], }); expect(result).toStrictEqual([ { name: "Main actions/checkout@v3", status: 0, output: "", }, { name: "Main github api call", status: 0, output: '{"full_name":"owner/project"}', }, { name: "Main custom api call", status: 0, output: "mock response", }, ]); });
Then run it:
$ npm test > non-custom-actions@1.0.0 test > jest PASS test/ci.test.ts ✓ api workflow (2365 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.24 s, estimated 5 s Ran all test suites.
Success! We can now more easily test any changes to our workflow file locally.
Conclusion
Mock-github and act-js enable us to take a test-driven approach to writing github actions while making sure that our git history remains clean.
Make sure to check out the repositories for these libraries along with some more complex examples:
Sobre el autor
Shubh Bapna is a Software Engineering Intern at Red Hat, where he is part of the Business Automation team.
Más similar
Navegar por canal
Automatización
Las últimas novedades en la automatización de la TI para los equipos, la tecnología y los entornos
Inteligencia artificial
Descubra las actualizaciones en las plataformas que permiten a los clientes ejecutar cargas de trabajo de inteligecia artificial en cualquier lugar
Nube híbrida abierta
Vea como construimos un futuro flexible con la nube híbrida
Seguridad
Vea las últimas novedades sobre cómo reducimos los riesgos en entornos y tecnologías
Edge computing
Conozca las actualizaciones en las plataformas que simplifican las operaciones en el edge
Infraestructura
Vea las últimas novedades sobre la plataforma Linux empresarial líder en el mundo
Aplicaciones
Conozca nuestras soluciones para abordar los desafíos más complejos de las aplicaciones
Programas originales
Vea historias divertidas de creadores y líderes en tecnología empresarial
Productos
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Servicios de nube
- Ver todos los productos
Herramientas
- Training y Certificación
- Mi cuenta
- Soporte al cliente
- Recursos para desarrolladores
- Busque un partner
- Red Hat Ecosystem Catalog
- Calculador de valor Red Hat
- Documentación
Realice pruebas, compras y ventas
Comunicarse
- Comuníquese con la oficina de ventas
- Comuníquese con el servicio al cliente
- Comuníquese con Red Hat Training
- Redes sociales
Acerca de Red Hat
Somos el proveedor líder a nivel mundial de soluciones empresariales de código abierto, incluyendo Linux, cloud, contenedores y Kubernetes. Ofrecemos soluciones reforzadas, las cuales permiten que las empresas trabajen en distintas plataformas y entornos con facilidad, desde el centro de datos principal hasta el extremo de la red.
Seleccionar idioma
Red Hat legal and privacy links
- Acerca de Red Hat
- Oportunidades de empleo
- Eventos
- Sedes
- Póngase en contacto con Red Hat
- Blog de Red Hat
- Diversidad, igualdad e inclusión
- Cool Stuff Store
- Red Hat Summit