{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/jest-and-github-actions",
    "result": {"data":{"site":{"siteMetadata":{"title":"your friend Joel's digital garden","description":"Articles and notes from a collaborator at egghead.io. Musings on software, business, and life from a skilled virtual assistant.","author":{"name":"Joel Hooks"},"keywords":["Video Blogger"]}},"mdx":{"excerpt":"If you want to jump right to the finish line and have an existing Github repository you want to run tests on, drop the following into a file here  .github/workflows/tests.yml  and you'll be running your tests whenever you push to your main…","fields":{"github":"https://github.com/joelhooks/joelhooks-com/tree/master/content/blog/2020-09-10--continuous-integration-with-jest-test-and-github-actions~~tBl-puhiR/index.mdx"},"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"id\": \"tBl-puhiR\",\n  \"slug\": \"jest-and-github-actions\",\n  \"date\": \"2020-09-10T00:00:00.000Z\",\n  \"title\": \"Continuous Integration with Jest Tests and Github Actions\",\n  \"published\": false\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"If you want to jump right to the finish line and have an existing Github repository you want to run tests on, drop the following into a file here \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".github/workflows/tests.yml\"), \" and you'll be running your tests whenever you push to your main branch or a PR is created.\"), mdx(\"p\", null, \"If you'd like to actually understand what is going on, keep scrolling!\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"name: Tests CI\\n\\non: [push, pull_request]\\n\\njobs:\\n  test:\\n    runs-on: ubuntu-latest\\n    steps:\\n      - uses: actions/checkout@v2\\n      - name: Test using Node.js\\n        uses: actions/setup-node@v1\\n        with:\\n          node-version: '12'\\n      - run: yarn install\\n      - run: yarn test:ci\\n\\n      - name: Tests \\u2705\\n        if: ${{ success() }}\\n        run: |\\n          curl --request POST \\\\\\n          --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \\\\\\n          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \\\\\\n          --header 'content-type: application/json' \\\\\\n          --data '{\\n            \\\"context\\\": \\\"tests\\\",\\n            \\\"state\\\": \\\"success\\\",\\n            \\\"description\\\": \\\"Tests passed\\\",\\n            \\\"target_url\\\": \\\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\\\"\\n          }'\\n\\n      - name: Tests \\uD83D\\uDEA8\\n        if: ${{ failure() }}\\n        run: |\\n          curl --request POST \\\\\\n          --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \\\\\\n          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \\\\\\n          --header 'content-type: application/json' \\\\\\n          --data '{\\n            \\\"context\\\": \\\"tests\\\",\\n            \\\"state\\\": \\\"failure\\\",\\n            \\\"description\\\": \\\"Tests failed\\\",\\n            \\\"target_url\\\": \\\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\\\"\\n          }'\\n\")), mdx(\"p\", null, \"This is a relatively generic template that sets up node (in this case v12) and creates an environment for you to execute commands in. In our case, the application is question is using Yarn and has a test command specifically set up for continuous integration or CI that is triggerd with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yarn test:ci\"), \".\"), mdx(\"p\", null, \"Here are the full docs for \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.github.com/en/actions/language-and-framework-guides/using-nodejs-with-github-actions\"\n  }, \"Using Node.js with GitHub Actions\"), \".\"), mdx(\"h2\", null, \"Updating the status of your commit\"), mdx(\"p\", null, \"Most of the time you'll probably want to use a workflow like this to test PRs and other commits to see if the tests past and protect against them being merged if they don't pass.\"), mdx(\"p\", null, \"Once the tests have finished we can use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success()\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure()\"), \" context (\", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions\"\n  }, \"context and expression syntax documentation for more on this\"), \") within the Github Action to conditionally execute a command based on that state. \"), mdx(\"p\", null, \"To update the status of the commit\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-0\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-0\",\n    \"className\": \"footnote-ref\"\n  }, \"0\")), \" we can \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"POST\"), \" a status update to the repo's status URL with the status of the test run.\"), mdx(\"p\", null, \"If you've protected the branch and require success for this workflow it should update and prevent merging until the tests pass!\"), mdx(\"p\", null, \"One thing to note here is that the workflow needs to be ran once with a status update for it to appear in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"require status checks to pass before merging\"), \" section of the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"branch protection rules\"), \" section of your Github repositories Branches setting.\"), mdx(\"h2\", null, \"Better Error Reporting\"), mdx(\"p\", null, \"One of the issues that you might run into is the error reporting of this workflow isn't great. Luckily we can solve this by creating a custom Jest test results reporter that will convert test results into a format that Github Actions can interpret and use to create more useful error messages.\"), mdx(\"p\", null, \"Save this to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"github-actions-reporter.js\"), \" in your project:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-javascript\"\n  }, \"class GithubActionsReporter {\\n  constructor(globalConfig, options) {\\n    this._globalConfig = globalConfig\\n    this._options = options\\n  }\\n\\n  onRunComplete(contexts, results) {\\n    results.testResults.forEach((testResultItem) => {\\n      const testFilePath = testResultItem.testFilePath\\n\\n      testResultItem.testResults.forEach((result) => {\\n        if (result.status !== 'failed') {\\n          return\\n        }\\n\\n        result.failureMessages.forEach((failureMessages) => {\\n          const newLine = '%0A'\\n          const message = failureMessages.replace(/\\\\n/g, newLine)\\n          const captureGroup = message.match(/:([0-9]+):([0-9]+)/)\\n\\n          if (!captureGroup) {\\n            console.log('Unable to extract line number from call stack')\\n            return\\n          }\\n\\n          const [, line, col] = captureGroup\\n          console.log(\\n            `::error file=${testFilePath},line=${line},col=${col}::${message}`,\\n          )\\n        })\\n      })\\n    })\\n  }\\n}\\n\\nmodule.exports = GithubActionsReporter\\n\")), mdx(\"p\", null, \"Credit to Github user stefanbuck\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-1\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-1\",\n    \"className\": \"footnote-ref\"\n  }, \"1\")), \" for the code\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-2\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-2\",\n    \"className\": \"footnote-ref\"\n  }, \"2\")), \" and rkusa\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-3\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-3\",\n    \"className\": \"footnote-ref\"\n  }, \"3\")), \" for the idea to use this approach\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-4\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-4\",\n    \"className\": \"footnote-ref\"\n  }, \"4\")), \". It relies on the [Github Action Workflow Command syntax\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-5\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-5\",\n    \"className\": \"footnote-ref\"\n  }, \"5\")), \", which is an esoteric eries of strings that you log to the console to get Github bots to do stuff for you \\uD83D\\uDE05\"), mdx(\"p\", null, \"To enable this you simply need to update your npm script so that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yarn test:ci\"), \" uses the custom reporter:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-json\"\n  }, \"{\\n  \\\"scripts\\\": {\\n    \\\"test\\\": \\\"jest --watch\\\",\\n    \\\"test:ci\\\": \\\"jest --ci --reporters='default' --reporters='./github-actions-reporter'\\\"\\n  }\\n}\\n\")), mdx(\"h2\", null, \"Testing Github Actions Locally\"), mdx(\"p\", null, \"The round trip to Github can make actions a real pain in the ass to test, but the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/nektos/act\"\n  }, \"act library\"), \" makes local testing relatively straight forward.\"), mdx(\"p\", null, \"This can be installed with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"brew install nektos/tap/act\"), \" (or the other ways described) quickly and give the core environment for running a local simulation of Github Workflows on your computer.\"), mdx(\"p\", null, \"If you don't already have it installed, Docker\", mdx(\"sup\", {\n    parentName: \"p\",\n    \"id\": \"fnref-6\"\n  }, mdx(\"a\", {\n    parentName: \"sup\",\n    \"href\": \"#fn-6\",\n    \"className\": \"footnote-ref\"\n  }, \"6\")), \" is required. You might also need to run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"docker pull buildpack-deps\"), \" to make sure the appropriate versions of Node are available for Docker.\"), mdx(\"p\", null, \"Now you can type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"act -j test\"), \" in the root of your projects and the Github Actions will run locally. Additional configuration is possible and might be required, but out of the box it provides useful feedback and avoids the round trip of triggering a Github Action by pushing changes to your repository.\"), mdx(\"div\", {\n    \"className\": \"footnotes\"\n  }, mdx(\"hr\", {\n    parentName: \"div\"\n  }), mdx(\"ol\", {\n    parentName: \"div\"\n  }, mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-0\"\n  }, \"Create a Commit Status documentation \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://docs.github.com/en/rest/reference/repos#create-a-commit-status\"\n  }, \"https://docs.github.com/en/rest/reference/repos#create-a-commit-status\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-0\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-1\"\n  }, \"Stefan Buck \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/stefanbuck\"\n  }, \"https://github.com/stefanbuck\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-1\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-2\"\n  }, \"A custom JSON Reporter for Jest so it will work with Github Actions \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/stefanbuck/jest-matcher/blob/82d50fdb31d8cdf12afb9c1da8120b4a738c01b1/json-reporter.js\"\n  }, \"https://github.com/stefanbuck/jest-matcher/blob/82d50fdb31d8cdf12afb9c1da8120b4a738c01b1/json-reporter.js\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-2\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-3\"\n  }, \"Markus Ast \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/rkusa\"\n  }, \"https://github.com/rkusa\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-3\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-4\"\n  }, \"Markus gives Stefan a handy link to some docs in a Github Issue \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/rkusa/jest-action/issues/4#issuecomment-576175353\"\n  }, \"https://github.com/rkusa/jest-action/issues/4#issuecomment-576175353\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-4\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-5\"\n  }, \"Github Workflow docs on setting an error message\", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message\"\n  }, \"https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-5\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")), mdx(\"li\", {\n    parentName: \"ol\",\n    \"id\": \"fn-6\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://hub.docker.com/editions/community/docker-ce-desktop-mac/\"\n  }, \"https://hub.docker.com/editions/community/docker-ce-desktop-mac/\"), mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"#fnref-6\",\n    \"className\": \"footnote-backref\"\n  }, \"\\u21A9\")))));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"Continuous Integration with Jest Tests and Github Actions","date":"September 10, 2020","banner":null,"slug":"jest-and-github-actions","keywords":null}}},"pageContext":{"id":"2a32d660-791f-5332-a5e7-fbe5fc16bbe1","prev":{"id":"957b3eab-cfa4-5c54-bf63-2581b6dedf14","parent":{"name":"index","sourceInstanceName":"blog"},"excerpt":"It's Monday morning. You have a sip of your favorite morning beverage and open your email client. 3632 emails from dependabot spanning every repository you've ever create on Github wait for you. What do you do? Most likely ignore them, but keeping…","fields":{"title":"Should I use automated dependency management?","slug":"automated-dependency-management","date":"2020-09-11T00:00:00.000Z"}},"next":{"id":"df08f616-1dda-5669-be0b-fe8f11f0dbb3","parent":{"name":"index","sourceInstanceName":"blog"},"excerpt":"The  useState  hook in React is a great way to persist and update data within your React components. A simple component might look like this: This approach works for most cases but relies on the value of count in the current render. In this second…","fields":{"title":"useState React hook allows updating state with a function","slug":"usestate-react-hook-allows-updating-state-with-a-function","date":"2020-09-03T00:00:00.000Z"}}}},
    "staticQueryHashes": ["1045846374"]}