1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([["7042"],{8149(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>c,toc:()=>l,assets:()=>a});var s=JSON.parse('{"id":"custom/testing","title":"Testing and Debugging","description":"Welcome to the guide on testing and debugging your Projen projects! If you\'re","source":"@site/../docs/custom/testing.md","sourceDirName":"custom","slug":"/custom/testing","permalink":"/docs/custom/testing","draft":false,"unlisted":false,"editUrl":"https://github.com/projen/projen/tree/main/docusaurus/../docs/custom/testing.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4},"sidebar":"docsSidebar","previous":{"title":"Programmatic API","permalink":"/docs/custom/programmatic-api"},"next":{"title":"Security","permalink":"/docs/security"}}'),o=n(4848),i=n(8453);let r={sidebar_position:4},c="Testing and Debugging",a={},l=[{value:"Testing",id:"testing",level:2},{value:"Snapshot testing",id:"snapshot-testing",level:3},{value:"Testing file creation",id:"testing-file-creation",level:3},{value:"Common Issues and Solutions",id:"common-issues-and-solutions",level:2},{value:"Non-Deterministic Snapshot Tests",id:"non-deterministic-snapshot-tests",level:3},{value:"Example solution",id:"example-solution",level:4}];function d(e){let t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",p:"p",pre:"pre",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"testing-and-debugging",children:"Testing and Debugging"})}),"\n",(0,o.jsx)(t.p,{children:"Welcome to the guide on testing and debugging your Projen projects! If you're\nnew to Projen or testing in general, don't worry \u2014 this page is designed to help\nyou understand and effectively use testing and debugging techniques in your projects."}),"\n",(0,o.jsx)(t.h2,{id:"testing",children:"Testing"}),"\n",(0,o.jsxs)(t.p,{children:["In Projen projects, the most common way to run tests is using a testing\nframework called ",(0,o.jsx)(t.a,{href:"https://jestjs.io/",children:"Jest"}),". Jest is a JavaScript Testing\nFramework with a focus on simplicity."]}),"\n",(0,o.jsx)(t.p,{children:"When you set up your Projen project, you can configure Jest in your projenrc.js\nfile like this:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:'const project = new NodeProject({\n // ...\n jestOptions: {\n jestConfig: {\n testMatch: ["**/*.test.ts"],\n },\n },\n});\n'})}),"\n",(0,o.jsx)(t.p,{children:"However, most of the time, the default Jest configuration works fine."}),"\n",(0,o.jsx)(t.p,{children:"jest or any other testing framework can be used to test projen projects. The\nfollowing sections will describe some of the common testing patterns in jest.\nFeel free to contribute best practices and tips for other testing frameworks."}),"\n",(0,o.jsx)(t.h3,{id:"snapshot-testing",children:"Snapshot testing"}),"\n",(0,o.jsx)(t.p,{children:"The most straightforward method for testing projen projects is to use jest's\nsnapshot testing feature. This feature allows you to take a snapshot of the\nproject's generated files and compare them to the expected output."}),"\n",(0,o.jsx)(t.p,{children:"Here's a basic example:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:'import { Testing } from "projen/lib/testing";\nimport { MyProject } from "../src/my-project";\n\ntest("snapshot", () => {\n const project = new MyProject();\n const snapshot = Testing.synth(project);\n expect(snapshot).toMatchSnapshot();\n});\n'})}),"\n",(0,o.jsx)(t.p,{children:"The above test will generate a snapshot of the entire project and compare\nit to the expected output. If the snapshot does not match, the test will fail."}),"\n",(0,o.jsx)(t.p,{children:"You'll quickly find that these types of snapshot tests are unwieldy - it captures\nthe entire project and it's hard to pinpoint exactly what changed. However, you\ncan adjust the test to look at the contents of a single file that was generated:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:'import { Testing } from "projen/lib/testing";\nimport { MyProject } from "../src/my-project";\n\ntest("snapshot", () => {\n const project = new MyProject();\n const snapshot = Testing.synth(project);\n expect(snapshot["README.md"]).toMatchSnapshot();\n});\n'})}),"\n",(0,o.jsxs)(t.p,{children:["This test will only compare the contents of the ",(0,o.jsx)(t.code,{children:"README.md"})," file to the expected\noutput. In other words, your snapshot file will only contain the contents of\nthe ",(0,o.jsx)(t.code,{children:"README.md"})," file and not the entire project tree."]}),"\n",(0,o.jsx)(t.admonition,{type:"note",children:(0,o.jsx)(t.p,{children:"Generally, if you just want to make sure that the contents of a single file\nare consistent, favor snapshot testing over comparing the contents directly."})}),"\n",(0,o.jsx)(t.h3,{id:"testing-file-creation",children:"Testing file creation"}),"\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.code,{children:"Testing.synth()"})," returns a map of all the files that were generated by the\nproject. You can use this map to identify whether specific files were created."]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:'import { Testing } from "projen/lib/testing";\nimport { MyProject } from "../src/my-project";\n\ntest("snapshot", () => {\n const project = new MyProject();\n const snapshot = Testing.synth(project);\n expect(snapshot[".github/workflows/release.yml"]).toBeDefined();\n});\n'})}),"\n",(0,o.jsx)(t.admonition,{type:"info",children:(0,o.jsx)(t.p,{children:"Note that this type of testing only identifies whether the file exists. It\ndoes not do any content comparison. You likely will want to use this type of\ntesting in conjunction with snapshot testing."})}),"\n",(0,o.jsx)(t.h2,{id:"common-issues-and-solutions",children:"Common Issues and Solutions"}),"\n",(0,o.jsx)(t.h3,{id:"non-deterministic-snapshot-tests",children:"Non-Deterministic Snapshot Tests"}),"\n",(0,o.jsx)(t.p,{children:"Snapshot tests should be predictable, but sometimes they fail randomly. This\ncan happen if your project attempts to modify a file that's already in your project tree."}),"\n",(0,o.jsxs)(t.p,{children:["For example, if your project is trying\nto create a ",(0,o.jsx)(t.code,{children:"README.md"})," file, but the project already has a ",(0,o.jsx)(t.code,{children:"README.md"})," file,\nyou either run into a race condition or one file will over
1write the other\nconsistently. This is especially common when extending existing projen\nprojects."]}),"\n",(0,o.jsxs)(t.p,{children:["Resolving this issue can be challenging. If you are extending an existing\nprojen project, you can try removing your ",(0,o.jsx)(t.code,{children:"README.md"})," file code and see if it still\nexists in the project tree with a snapshot test. If it does, then something else\nin your project code is already creating the file, and you'll need to identify\nwhat that is and either remove it or override it."]}),"\n",(0,o.jsx)(t.h4,{id:"example-solution",children:"Example solution"}),"\n",(0,o.jsxs)(t.p,{children:["In the example below, we're extending the ",(0,o.jsx)(t.code,{children:"NodeProject"})," class, which already\ncreates a ",(0,o.jsx)(t.code,{children:"README.md"})," file. In fact, all classes that extend ",(0,o.jsx)(t.code,{children:"GitHubProject"}),"\nwill create a ",(0,o.jsx)(t.code,{children:"README.md"})," file by default. This behavior is controlled by the\n",(0,o.jsx)(t.code,{children:"readme"})," option in the ",(0,o.jsx)(t.code,{children:"GitHubProjectOptions"})," interface."]}),"\n",(0,o.jsxs)(t.p,{children:["We can remove the ",(0,o.jsx)(t.code,{children:"README.md"})," file from the base project, which should allow\nthe one we've hypothetically added in ",(0,o.jsx)(t.code,{children:"CustomNodeProject"})," to be created."]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:"const project = new CustomNodeProject({\n // ...\n readme: false, // This will disable the readme file in the underlying NodeProject\n});\n\nproject.synth();\n"})}),"\n",(0,o.jsxs)(t.p,{children:["Alternately, since we know that the ",(0,o.jsx)(t.code,{children:"README.md"})," file is already being created,\nwe can remove the code that creates it in ",(0,o.jsx)(t.code,{children:"CustomNodeProject"})," and just use the\none from the base class."]}),"\n",(0,o.jsxs)(t.p,{children:["For classes that do not have a ",(0,o.jsx)(t.code,{children:"readme"})," option, you can use the ",(0,o.jsx)(t.code,{children:"tryRemoveFile"}),"\noption to remove the file from the project tree."]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",children:'const project = new CustomNodeProject({\n // ...\n});\nproject.tryRemoveFile("README.md");\n'})}),"\n",(0,o.jsxs)(t.p,{children:["Note, however, that this will probably also remove the ",(0,o.jsx)(t.code,{children:"README.md"})," file created\nfrom ",(0,o.jsx)(t.code,{children:"CustomNodeProject"}),", so in this particular case, it's probably better to\njust disable the ",(0,o.jsx)(t.code,{children:"README.md"})," file in the base class or use it instead of a\ncustom implementation."]})]})}function h(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453(e,t,n){n.d(t,{R:()=>r,x:()=>c});var s=n(6540);let o={},i=s.createContext(o);function r(e){let t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.