{"id":18260033,"url":"https://github.com/roppa/tdd-fundamentals","last_synced_at":"2025-04-04T19:33:09.658Z","repository":{"id":44064848,"uuid":"212914624","full_name":"roppa/tdd-fundamentals","owner":"roppa","description":"Test Driven Development fundamentals wtih Javascript and Jest","archived":false,"fork":false,"pushed_at":"2023-01-04T12:06:04.000Z","size":265,"stargazers_count":14,"open_issues_count":12,"forks_count":8,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-03-23T12:10:14.194Z","etag":null,"topics":["game-of-life","javascript","jest","tdd","test","test-driven-development"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roppa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-04T22:54:13.000Z","updated_at":"2023-03-05T02:24:10.000Z","dependencies_parsed_at":"2023-02-02T11:30:34.028Z","dependency_job_id":null,"html_url":"https://github.com/roppa/tdd-fundamentals","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roppa%2Ftdd-fundamentals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roppa%2Ftdd-fundamentals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roppa%2Ftdd-fundamentals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roppa%2Ftdd-fundamentals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roppa","download_url":"https://codeload.github.com/roppa/tdd-fundamentals/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223156904,"owners_count":17097300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game-of-life","javascript","jest","tdd","test","test-driven-development"],"created_at":"2024-11-05T10:41:37.338Z","updated_at":"2024-11-05T10:41:37.894Z","avatar_url":"https://github.com/roppa.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Javascript Test Driven Development for the real world\n\n**Pre-requisites**: intermediate level of javascript and npm\n\n**Goal**: to create a working example of 'The Game of Life' by using Test Driven Techniques\n\n**Purpose**: to get a working understanding of tests, different types of tests, and test driven development\n\n**Tools**: Node.js (\u003e v8.4.0), an IDE such as VS Code, a modern Browser such as Chrome, Firefox, later version of IE\n\n## Definitions\n\n\u003cdl\u003e\n  \u003cdt\u003eAssertion\u003c/dt\u003e\n  \u003cdd\u003eto state something confidently and forcefully. This means stating an 'is-ness'; 'This is WRONG'. This then equates to a boolean, is it true or false? true would mean a passing test\u003c/dd\u003e\n  \u003cdt\u003eInterface\u003c/dt\u003e\n  \u003cdd\u003ea part of software or hardware that facilitates communication\u003c/dd\u003e\n  \u003cdt\u003eUnit test\u003c/dt\u003e\n  \u003cdd\u003efocus on one part of the software. A unit test can be run in isolation - it must not rely on the state of any previous tests\u003c/dd\u003e\n  \u003cdt\u003eRegression testing\u003c/dt\u003e\n  \u003cdd\u003erunning existing tests to ensure that software is working after a change\u003c/dd\u003e\n  \u003cdt\u003eIntegration test\u003c/dt\u003e\n  \u003cdd\u003emodules, functions etc tested as a group\u003c/dd\u003e\n  \u003cdt\u003eStub\u003c/dt\u003e\n  \u003cdd\u003ea common test helper (could be a database connector, api endpoint) providing hard coded answers\u003c/dd\u003e\n  \u003cdt\u003eSpy\u003c/dt\u003e\n  \u003cdd\u003esimilar to stub, they record information they were called with\u003c/dd\u003e\n  \u003cdt\u003eMock\u003c/dt\u003e\n  \u003cdd\u003emimic exactly the object they are replacing, returning specific responses. More than just a stub\u003c/dd\u003e\n  \u003cdt\u003ePerformance test/Load test\u003c/dt\u003e\n  \u003cdd\u003emeasuring how well code copes with stress\u003c/dd\u003e\n  \u003cdt\u003eCode coverage\u003c/dt\u003e\n  \u003cdd\u003emeasures how many times a line, statement, branch (if/else) or function etc is executed\u003c/dd\u003e\n  \u003cdt\u003eE2E (end to end) testing\u003c/dt\u003e\n  \u003cdd\u003etesting a user journey through software from start to achieving the journeys goal\u003c/dd\u003e\n\u003c/dl\u003e\n\nWhy Test Driven Development? Most companies seek people with TDD skills, why is this? TDD does not mean 'your code has tests'. It doesn't really mean you have 'experience of using X testing library'. It means you actually write your tests first and write code to pass those test systematically.\n\nWhat teams actually want from a prospective engineer is:\n\n- ability solve a problem in a systematic way\n- produce clean code\n- produce code which communicates to the next developer (which could also be you) exactly what the problem was\n\nThe TDD technique helps to achieve all of the above.\n\n## Technique\n\nTest Driven Development is a technique. A tool is something that makes your life easier. A technique is how to best use the tool. The tool is the assertion library and testing framework.\n\nRules for test driven development are:\n\n- write a failing test\n- write code to pass the test\n- refactor (remove duplication)\n\nThis is the base for the three laws of TDD:\n\n1. You are not allowed to write production code unless it is to make a failing unit test pass\n2. You are not allowed to write any more of a unit test than is sufficient to fail; not compiling is failing\n3. You are not allowed to write more production code than is sufficient to pass the one failing unit test\n\nWhy fail first?\n\n- to see regression tests fail (no 0 positives)\n- it makes you think about your code\n- your code will be testable (you don't want to write code then have to refactor in order to be able to test)\n\nThere is another part to this technique. Uncle Bob put it best 'as the tests get more specific, the production code gets more generic'. This ties in with the refactoring part of TDD as we'll see later.\n\n## Application of TDD\n\nFirst we have a goal 'build an x' - in our case 'build a game of life'. The goal is the end result; the product. This would then be broken down into individual tasks (usually when you work for a company you may only get individual tasks such as 'As an x I want to y so that I can z'). After selecting a task to work on you ask yourself what set of tests would confidently demonstrate that this task is complete and works as expected. This process could be:\n\n- break down the task and list the tests we need for it to be considered 'done'\n- 'start small or not at all' as Kent Beck says - so write the simplest test and code to pass\n- stub any dependencies\n- incrementally change constants to variables (from specific to generic)\n\n## Testing frameworks\n\nThere are many testing frameworks out there but they all consist of some or all of these tools:\n\n- testing harness\n- assertion library\n- reporting\n- code coverage\n\nWe will use [Jest](https://jestjs.io/en/) as our library because it covers everything above and more including: mocks, spies, good documentation - and we can test both front and back end code.\n\n## Our task\n\nOur task is to implement the [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life) using TDD. First we will build the engine, and then we will build a front end solution. We're not going to get into functional programming, Object Oriented programming etc, for this we'll just be concentrating on writing tests and breaking a problem down into component parts.\n\nCheck out the [completed version](./completed) of the project to get an idea what it is. The game is grid based consisting of cells that are either alive or dead. Through each cycle of life this algorithm applies:\n\n- any live cell with fewer than two live neigbours dies, as if by under-population\n- any live cell with two or three live neighbours lives on to the next generation\n- any live cell with more than three live neighbours dies, as if by overpopulation\n- any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction\n\nFor our example we want to see the game in a browser. This is one of the great things about Jest - browser objects such as `window` and `document` are already stubbed for us.\n\nWe have a basic bootstrap for our project, an html page (includes styles and basic controls), empty test, and the main game file in the [game-of-life folder](./game-of-life). A convention for Jest is to put test files in a `__tests__` folder, so a starting test file exists there.\n\n## Running tests\n\nOnce we have run `npm install` we can being testing using `jest` from the command line. Running this command after each change is quite tedious, so jest has a `--watch` flag to make tests run when it detects any change to the code.\n\nWe can also use Jest to check our code coverage using the `coverage` flag: `jest --coverage`. Looking good! Lets now start working on implementing a grid for the game.\n\n## DOM\n\nJest comes with a **mock** DOM environment, `jsdom`, which simulates the DOM as if you were in the browser! Again, this is one of the great things about Jest - it covers a lot of bases.\n\n## Introduction to a test structure\n\nBefore jumping into the tests, we'll cover how to create a test file and structure our tests. Check out the [test-example folder](./test-example).\n\n### Definitions\n\n\u003cdl\u003e\n  \u003cdt\u003e\u003ccode\u003etest\u003c/code\u003e (or the alias \u003ccode\u003eit\u003c/code\u003e)\u003c/dt\u003e\n  \u003cdd\u003ethe only part of a test you need, as it runs the assertions within it\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003edescribe\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ecreates a code block grouping together several related tests\n  \u003cdt\u003e\u003ccode\u003ebeforeAll\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ea function ran before all tests in the current file are run. This helps with resetting variables or performing initiation tasks\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003eafterAll\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis is ran after all the tests in the current file have been run. Can be used to clean up environment etc\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003eskip\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis makes jest ignore the test e.g. \u003ccode\u003etest.skip(...\u003c/code\u003e\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003eonly\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis ignores all tests in the file apart from the one it is called on e.g. \u003ccode\u003etest.only(...\u003c/code\u003e\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003ebeforeEach\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis runs a function before every test in the current file. This is helpful for maintaining a default state\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003eafterEach\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis runs a function after each test in the current file\u003c/dd\u003e\n  \u003cdt\u003e\u003ccode\u003eexpect\u003c/code\u003e\u003c/dt\u003e\n  \u003cdd\u003ethis is the assertion function\u003c/dd\u003e\n\u003c/dl\u003e\n\nTo start, your tests would be in a folder where your code will be. This makes your code more modular. Inside your code folder you would have a nested folder named `__tests__` where you put your individual test files. You can suffix the same filename you are testing with `.test` or `.spec`, so for example `game.js` would become `game.spec.js`. This helps to see what tests files relates to if you have multiple files.\n\nThere is something called 'AAA' - Arrange, Act, Assert. It seems somewhat common sense and we find ourselves falling into this pattern naturally:\n\n- **Arrange**: setup for your test; the variables or environment your test depends on\n- **Act**: calling the function/method you are testing\n- **Assert**: check the expectations\n\n`expect().toBe()` and `expect().toEqual()` both use [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) for comparasons. However, `expect().toBe()` is used for primitive values, whereas `expect().toEqual()` performs a 'deep' comparason so you can compare two different objects for the same values. Check out [the jest documentation](https://jestjs.io/docs/en/expect) for further methods.\n\n## Example of TDD\n\nWe're going to start with the Game of Life algorithm and build it using TDD.\n\n### Game algorithm\n\nSo, starting small or not at all, we see that we need 2 parameters, whether a cell is alive or dead, and a number of neighbours.\n\nThere is one condition that isn't explicitly declared - a dead cell with no neighbours. Lets take this for our first test:\n\n```js\ndescribe(\"game of life\", () =\u003e {\n  describe(\"isAlive algorithm\", () =\u003e {\n    it(\"should return 0 when dead cell (0) with 0 neighbours\", () =\u003e {\n      expect(isAlive(0, 0)).toBe(0);\n    });\n  });\n});\n```\n\nThen run with `jest`. You should see a failure. Now we can write just enough code to pass the test:\n\n```js\nfunction isAlive() {\n  return 0;\n}\n\nwindow.game = {\n  isAlive\n};\n```\n\nWhat? I just returned 0! What is the point of that? Well, baby steps. We want to go red to green, red to green in fast short iterations.\n\nNext test, sticking with dead cells, the next test could be for exactly 3 neighbours.\n\n```js\nit(\"should return 1 when dead cell with exactly 3 neighbours\", () =\u003e {\n  expect(isAlive(0, 3)).toBe(1);\n});\n```\n\n```js\nfunction isAlive(cell, neighbours) {\n  if (neighbours === 3) {\n    return 1;\n  }\n  return 0;\n}\n```\n\nPass! Great! Next tests: live cells.\n\n```js\nit(\"should return 0 when live cell with \u003c 2 neighbours\", () =\u003e {\n  expect(isAlive(1, 0)).toBe(0);\n});\n```\n\nWait, our test passes? We should write a breaking test first? Well, actually looking at the algorithm, it says a live or dead cell with exactly 3 neighbours.\n\nNow to test a live cell with 2 or 3 neighbours:\n\n```js\nit(\"should return 1 when live cell with 2 or 3 neighbours\", () =\u003e {\n  expect(isAlive(1, 2)).toBe(1);\n});\n```\n\n```js\nfunction isAlive(cell, neighbours) {\n  if (Boolean(cell) \u0026\u0026 neighbours === 2) {\n    return 1;\n  }\n  if (neighbours === 3) {\n    return 1;\n  }\n  return 0;\n}\n```\n\nNow our tests pass. Our code is pretty messy though! This is where we refactor, and we are safe to do so because we have our tests to ensure things are correct.\n\n```js\nfunction isAlive(cell, neighbours) {\n  if ((Boolean(cell) \u0026\u0026 neighbours === 2) || neighbours === 3) {\n    return 1;\n  }\n  return 0;\n}\n```\n\nGreat! A lot cleaner! In fact we can further optimise this code, even using arrow function to remove the `return` statement:\n\n```js\nconst isAlive = (cell, neighbours) =\u003e\n  (Boolean(cell) \u0026\u0026 neighbours === 2) || neighbours === 3 ? 1 : 0;\n```\n\nRemember, as the tests get more specific the code becomes more generic.\n\nWe could even update our first test to add further test cases:\n\n```js\ndescribe(\"algorithm\", () =\u003e {\n  it(\"should return 0 when dead cell with \u003e 3 or \u003c 3 neighbours\", () =\u003e {\n    expect(isAlive(0, 0)).toBe(0);\n    expect(isAlive(0, 1)).toBe(0);\n    expect(isAlive(0, 2)).toBe(0);\n    expect(isAlive(0, 4)).toBe(0);\n  });\n});\n```\n\nThe solution looks so simple compared to my initial apprehension of complexity of the game. Confidence comes from the tests.\n\n### Cells\n\nFor us to apply the algorithm we need a cell with neighbours, so lets work on that next. Lets call the function `generate`, and taking a square root.\n\n```js\ndescribe(\"generate cells\", () =\u003e {\n  it(\"should return array of length * length\", () =\u003e {\n    const cells = generate(1);\n    expect(cells).toEqual([0]);\n  });\n});\n```\n\n## Course\n\nThe above is a small sample of the [TDD using Javascript and Jest course on Udemy](https://www.udemy.com/course/test-driven-development-using-javascript-and-jest/) which goes into more details about the solution.\n\n## References\n\n- [Conway's game of life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life)\n- [Kent Beck - Test Driven Development](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)\n- [Uncle Bob](https://blog.cleancoder.com/)\n- [Martin Fowler](https://martinfowler.com/)\n- [Jest](https://jestjs.io/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froppa%2Ftdd-fundamentals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froppa%2Ftdd-fundamentals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froppa%2Ftdd-fundamentals/lists"}