{"id":14989719,"url":"https://github.com/learning-zone/javascript-unit-testing","last_synced_at":"2025-07-11T14:37:41.087Z","repository":{"id":41685681,"uuid":"196711706","full_name":"learning-zone/javascript-unit-testing","owner":"learning-zone","description":"JavaScript Unit Testing","archived":false,"fork":false,"pushed_at":"2023-03-03T14:44:09.000Z","size":857,"stargazers_count":75,"open_issues_count":70,"forks_count":34,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-19T08:50:06.719Z","etag":null,"topics":["chai","javascript-tools","karma-jasmine","mocha-tests","sinonjs","unit-test"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/learning-zone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-13T11:01:56.000Z","updated_at":"2024-12-30T07:03:28.000Z","dependencies_parsed_at":"2024-09-25T00:34:46.290Z","dependency_job_id":null,"html_url":"https://github.com/learning-zone/javascript-unit-testing","commit_stats":{"total_commits":54,"total_committers":1,"mean_commits":54.0,"dds":0.0,"last_synced_commit":"2bce97478b1231a70d5ef1a572bf43b7078dfd83"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/learning-zone/javascript-unit-testing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learning-zone%2Fjavascript-unit-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learning-zone%2Fjavascript-unit-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learning-zone%2Fjavascript-unit-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learning-zone%2Fjavascript-unit-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/learning-zone","download_url":"https://codeload.github.com/learning-zone/javascript-unit-testing/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learning-zone%2Fjavascript-unit-testing/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264833291,"owners_count":23670617,"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":["chai","javascript-tools","karma-jasmine","mocha-tests","sinonjs","unit-test"],"created_at":"2024-09-24T14:18:49.225Z","updated_at":"2025-07-11T14:37:41.065Z","avatar_url":"https://github.com/learning-zone.png","language":"JavaScript","readme":"# JavaScript Unit Testing\n\n\u003e *Click \u0026#9733; if you like the project. Your contributions are heartily ♡ welcome.*\n\n\u003cbr/\u003e\n\n**Unit = Unit of work**\n\nThis could involve **multiple methods and classes** invoked by some public API that can:\n\n+ Return a value or throw an exception\n+ Change the state of the system\n+ Make 3rd party calls (API, database, ...)\n\nA unit test should test the behaviour of a unit of work: for a given input, it expects an end result that can be any of the above.\n\n**Unit tests are isolated and independent of each other**\n\n+ Any given behaviour should be specified in **one and only one test**\n+ The execution/order of execution of one test **cannot affect the others**\n\nThe code is designed to support this independence (see \"Design principles\" below).\n\n**Unit tests are lightweight tests**\n\n+ Repeatable\n+ Fast\n+ Consistent\n+ Easy to write and read\n\n**Unit tests are code too**\n\nThey should meet the same level of quality as the code being tested. They can be refactored as well to make them more maintainable and/or readable.\n\n\n\n### Design principles\n\nThe key to good unit testing is to write **testable code**. Applying simple design principles can help, in particular:\n\n+ Use a **good naming** convention and **comment** your code (the \"why?\" not the \"how\"), keep in mind that comments are not a substitute for bad naming or bad design\n+ **DRY**: Don't Repeat Yourself, avoid code duplication\n+ **Single responsibility**: each object/function must focus on a single task\n+ Keep a **single level of abstraction** in the same component (for example, do not mix business logic with lower-level technical details in the same method)\n+ **Minimize dependencies** between components: encapsulate, interchange less information between components\n+ **Support configurability** rather than hard-coding, this prevents having to replicate the exact same environment when testing (e.g.: markup)\n+ Apply adequate **design patterns**, especially **dependency injection** that allows separating an object's creation responsibility from business logic\n+ Avoid global mutable state\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n## Guidelines\n\nThe goal of these guidelines is to make your tests:\n\n+ **Readable**\n+ **Maintainable**\n+ **Trustworthy**\n\nThese are the 3 pillars of good unit testing.\n\nAll the following examples assume the usage of the [Jasmine](http://jasmine.github.io) framework.\n\n\n### Whenever possible, use TDD\n\nTDD is a _design process_, not a testing process. TDD is a robust way of designing software components (\"units\") interactively so that their behaviour is specified through unit tests.\n\nHow? Why?\n\n#### Test-first cycle\n\n1. Write a simple failing test\n2. Make the test pass by writing the minimum amount of code, don't bother with code quality\n3. Refactor the code by applying design principles/patterns\n\n#### Consequences of the test-first cycle\n\n+ Writing a test first makes the code design testable de facto\n+ Writing just the amount of code needed to implement the required functionality makes the resulting codebase minimal, thus more maintainable\n+ The codebase can be enhanced using refactoring mechanisms, the tests give you confidence that the new code is not modifying the existing functionalities\n+ Cleaning the code in each cycle makes the codebase more maintainable, it is much cheaper to change the code frequently and in small increments\n+ Fast feedback for the developers, you know that you don't break anything and that you are evolving the system in a good direction\n+ Generates confidence to add features, fix bugs, or explore new designs\n\nNote that code written without a test-first approach is often very hard to test.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Structure your tests properly\n\nDon't hesitate to nest your suites to structure logically your tests in subsets.\n\n**:(**\n\n```js\ndescribe('A set of functionalities', () =\u003e {\n  it('a set of functionalities should do something nice', () =\u003e {\n  });\n\n  it('a subset of functionalities should do something great', () =\u003e {\n  });\n\n  it('a subset of functionalities should do something awesome', () =\u003e {\n  });\n\n  it('another subset of functionalities should also do something great', () =\u003e {\n  });\n});\n```\n\n**:)**\n\n```js\ndescribe('A set of functionalities', () =\u003e {\n  it('should do something nice', () =\u003e {\n  });\n\n  describe('A subset of functionalities', () =\u003e {\n    it('should do something great', () =\u003e {\n    });\n\n    it('should do something awesome', () =\u003e {\n    });\n  });\n\n  describe('Another subset of functionalities', () =\u003e {\n    it('should also do something great', () =\u003e {\n    });\n  });\n});\n```\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Name your tests properly\n\nTests names should be concise, explicit, descriptive and in correct English. Read the output of the spec runner and verify that it is understandable! Keep in mind that someone else will read it too. Tests can be the live documentation of the code.\n\n**:(**\n\n```js\ndescribe('MyGallery', () =\u003e {\n  it('init set correct property when called (thumb size, thumbs count)', () =\u003e {\n  });\n\n  // ...\n});\n```\n\n**:)**\n\n```js\ndescribe('The Gallery instance', () =\u003e {\n  it('should properly calculate the thumb size when initialized', () =\u003e {\n  });\n\n  it('should properly calculate the thumbs count when initialized', () =\u003e {\n  });\n\n  // ...\n});\n```\n\nIn order to help you write test names properly, you can use the **\"unit of work - scenario/context - expected behaviour\"** pattern:\n\n```js\ndescribe('[unit of work]', () =\u003e {\n  it('should [expected behaviour] when [scenario/context]', () =\u003e {\n  });\n});\n```\n\nOr whenever you have many tests that follow the same scenario or are related to the same context:\n\n```js\ndescribe('[unit of work]', () =\u003e {\n  describe('when [scenario/context]', () =\u003e {\n    it('should [expected behaviour]', () =\u003e {\n    });\n  });\n});\n```\n\nFor example:\n\n**:) :)**\n\n```js\ndescribe('The Gallery instance', () =\u003e {\n  describe('when initialized', () =\u003e {\n    it('should properly calculate the thumb size', () =\u003e {\n    });\n\n    it('should properly calculate the thumbs count', () =\u003e {\n    });\n  });\n\n  // ...\n});\n```\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Don't comment out tests\n\nNever. Ever. Tests have a reason to be or not.\n\nDon't comment them out because they are too slow, too complex or produce false negatives. Instead, make them fast, simple and trustworthy. If not, remove them completely.\n\n\n\n### Avoid logic in your tests\n\nAlways use simple statements. Don't use loops and/or conditionals. If you do, you add a possible entry point for bugs in the test itself:\n\n+ Conditionals: you don't know which path the test will take\n+ Loops: you could be sharing state between tests\n\n**:(**\n\n```js\nit('should properly sanitize strings', () =\u003e {\n  let result;\n  const testValues = {\n    'Avion'         : 'Avi' + String.fromCharCode(243) + 'n',\n    'The-space'     : 'The space',\n    'Weird-chars-'  : 'Weird chars!!',\n    'file-name.zip' : 'file name.zip',\n    'my-name.zip'   : 'my.name.zip'\n  };\n\n  for (result in testValues) {\n    expect(sanitizeString(testValues[result])).toBe(result);\n  }\n});\n```\n\n**:)**\n\n```js\nit('should properly sanitize strings', () =\u003e {\n  expect(sanitizeString('Avi'+String.fromCharCode(243)+'n')).toBe('Avion');\n  expect(sanitizeString('The space')).toBe('The-space');\n  expect(sanitizeString('Weird chars!!')).toBe('Weird-chars-');\n  expect(sanitizeString('file name.zip')).toBe('file-name.zip');\n  expect(sanitizeString('my.name.zip')).toBe('my-name.zip');\n});\n```\n\nBetter: write a test for each type of sanitization. It will give a nice output of all possible cases, improving maintainability.\n\n**:) :)**\n\n```js\nit('should sanitize a string containing non-ASCII chars', () =\u003e {\n  expect(sanitizeString('Avi'+String.fromCharCode(243)+'n')).toBe('Avion');\n});\n\nit('should sanitize a string containing spaces', () =\u003e {\n  expect(sanitizeString('The space')).toBe('The-space');\n});\n\nit('should sanitize a string containing exclamation signs', () =\u003e {\n  expect(sanitizeString('Weird chars!!')).toBe('Weird-chars-');\n});\n\nit('should sanitize a filename containing spaces', () =\u003e {\n  expect(sanitizeString('file name.zip')).toBe('file-name.zip');\n});\n\nit('should sanitize a filename containing more than one dot', () =\u003e {\n  expect(sanitizeString('my.name.zip')).toBe('my-name.zip');\n});\n```\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Don't write unnecessary expectations\n\nRemember, unit tests are a design specification of how a certain *behaviour* should work, not a list of observations of everything the code happens to do.\n\n**:(**\n\n```js\nit('should multiply the number passed as parameter and subtract one', () =\u003e {\n  const multiplySpy = spyOn(Calculator, 'multiple').and.callThrough();\n  const subtractSpy = spyOn(Calculator, 'subtract').and.callThrough();\n\n  const result = Calculator.compute(21.5);\n\n  expect(multiplySpy).toHaveBeenCalledWith(21.5, 2);\n  expect(subtractSpy).toHaveBeenCalledWith(43, 1);\n  expect(result).toBe(42);\n});\n```\n\n**:)**\n\n```js\nit('should multiply the number passed as parameter and subtract one', () =\u003e {\n  const result = Calculator.compute(21.5);\n  expect(result).toBe(42);\n});\n```\n\nThis will improve maintainability. Your test is no longer tied to implementation details.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Properly setup the actions that apply to all the tests involved\n\n**:(**\n\n```js\ndescribe('Saving the user profile', () =\u003e {\n  let profileModule;\n  let notifyUserSpy;\n  let onCompleteSpy;\n\n  beforeEach(() =\u003e {\n    profileModule = new ProfileModule();\n    notifyUserSpy = spyOn(profileModule, 'notifyUser');\n    onCompleteSpy = jasmine.createSpy();\n  });\n\n  it('should send the updated profile data to the server', () =\u003e {\n    jasmine.Ajax.install();\n\n    profileModule.save();\n\n    const request = jasmine.Ajax.requests.mostRecent();\n\n    expect(request.url).toBe('/profiles/1');\n    expect(request.method).toBe('POST');\n    expect(request.data()).toEqual({ username: 'mawrkus' });\n\n    jasmine.Ajax.uninstall();\n  });\n\n  it('should notify the user', () =\u003e {\n    jasmine.Ajax.install();\n\n    profileModule.save();\n\n    expect(notifyUserSpy).toHaveBeenCalled();\n\n    jasmine.Ajax.uninstall();\n  });\n\n  it('should properly execute the callback passed as parameter', () =\u003e {\n    jasmine.Ajax.install();\n\n    profileModule.save(onCompleteSpy);\n\n    jasmine.Ajax.uninstall();\n\n    expect(onCompleteSpy).toHaveBeenCalled();\n  });\n});\n```\n\nThe setup code should apply to all the tests:\n\n**:)**\n\n```js\ndescribe('Saving the user profile', () =\u003e {\n  let profileModule;\n\n  beforeEach(() =\u003e {\n    jasmine.Ajax.install();\n    profileModule = new ProfileModule();\n  });\n\n  afterEach( () =\u003e {\n    jasmine.Ajax.uninstall();\n  });\n\n  it('should send the updated profile data to the server', () =\u003e {\n    profileModule.save();\n\n    const request = jasmine.Ajax.requests.mostRecent();\n\n    expect(request.url).toBe('/profiles/1');\n    expect(request.method).toBe('POST');\n\n  });\n\n  it('should notify the user', () =\u003e {\n    spyOn(profileModule, 'notifyUser');\n\n    profileModule.save();\n\n    expect(profileModule.notifyUser).toHaveBeenCalled();\n  });\n\n  it('should properly execute the callback passed as parameter', () =\u003e {\n    const onCompleteSpy = jasmine.createSpy();\n\n    profileModule.save(onCompleteSpy);\n\n    expect(onCompleteSpy).toHaveBeenCalled();\n  });\n});\n```\n\nConsider keeping the setup code minimal to preserve readability and maintainability.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Consider using factory functions in the tests\n\nFactories can:\n\n+ help reduce the setup code, especially if you use dependency injection\n+ make each test more readable, since the creation is a single function call that can be in the test itself instead of the setup\n+ provide flexibility when creating new instances (setting an initial state, for example)\n\nThere's a trade-off to find here between applying the DRY principle and readability.\n\n**:(**\n\n```js\ndescribe('User profile module', () =\u003e {\n  let profileModule;\n  let pubSub;\n\n  beforeEach(() =\u003e {\n    const element = document.getElementById('my-profile');\n    pubSub = new PubSub({ sync: true });\n\n    profileModule = new ProfileModule({\n      element,\n      pubSub,\n      likes: 0\n    });\n  });\n\n  it('should publish a topic when a new \"like\" is given', () =\u003e {\n    spyOn(pubSub, 'notify');\n    profileModule.incLikes();\n    expect(pubSub.notify).toHaveBeenCalledWith('likes:inc', { count: 1 });\n  });\n\n  it('should retrieve the correct number of likes', () =\u003e {\n    profileModule.incLikes();\n    profileModule.incLikes();\n    expect(profileModule.getLikes()).toBe(2);\n  });\n});\n```\n\n**:)**\n\n```js\ndescribe('User profile module', () =\u003e {\n  function createProfileModule({\n    element = document.getElementById('my-profile'),\n    likes = 0,\n    pubSub = new PubSub({ sync: true })\n  }) {\n    return new ProfileModule({ element, likes, pubSub });\n  }\n\n  it('should publish a topic when a new \"like\" is given', () =\u003e {\n    const pubSub = jasmine.createSpyObj('pubSub', ['notify']);\n    const profileModule = createProfileModule({ pubSub });\n\n    profileModule.incLikes();\n\n    expect(pubSub.notify).toHaveBeenCalledWith('likes:inc');\n  });\n\n  it('should retrieve the correct number of likes', () =\u003e {\n    const profileModule = createProfileModule({ likes: 40 });\n\n    profileModule.incLikes();\n    profileModule.incLikes();\n\n    expect(profileModule.getLikes()).toBe(42);\n  });\n});\n```\n\nFactories are particularly useful when dealing with the DOM:\n\n**:(**\n\n```js\ndescribe('The search component', () =\u003e {\n  describe('when the search button is clicked', () =\u003e {\n    let container;\n    let form;\n    let searchInput;\n    let submitInput;\n\n    beforeEach(() =\u003e {\n      fixtures.inject(`\u003cdiv id=\"container\"\u003e\n        \u003cform class=\"js-form\" action=\"/search\"\u003e\n          \u003cinput type=\"search\"\u003e\n          \u003cinput type=\"submit\" value=\"Search\"\u003e\n        \u003c/form\u003e\n      \u003c/div\u003e`);\n\n      container = document.getElementById('container');\n      form = container.getElementsByClassName('js-form')[0];\n      searchInput = form.querySelector('input[type=search]');\n      submitInput = form.querySelector('input[type=submith]');\n    });\n\n    it('should validate the text entered', () =\u003e {\n      const search = new Search({ container });\n      spyOn(search, 'validate');\n\n      search.init();\n\n      input(searchInput, 'peace');\n      click(submitInput);\n\n      expect(search.validate).toHaveBeenCalledWith('peace');\n    });\n\n    // ...\n  });\n});\n```\n\n**:)**\n\n```js\nfunction createHTMLFixture() {\n  fixtures.inject(`\u003cdiv id=\"container\"\u003e\n    \u003cform class=\"js-form\" action=\"/search\"\u003e\n      \u003cinput type=\"search\"\u003e\n      \u003cinput type=\"submit\" value=\"Search\"\u003e\n    \u003c/form\u003e\n  \u003c/div\u003e`);\n\n  const container = document.getElementById('container');\n  const form = container.getElementsByClassName('js-form')[0];\n  const searchInput = form.querySelector('input[type=search]');\n  const submitInput = form.querySelector('input[type=submith]');\n\n  return {\n    container,\n    form,\n    searchInput,\n    submitInput\n  };\n}\n\ndescribe('The search component', () =\u003e {\n  describe('when the search button is clicked', () =\u003e {\n    it('should validate the text entered', () =\u003e {\n      const { container, form, searchInput, submitInput } = createHTMLFixture();\n      const search = new Search({ container });\n      spyOn(search, 'validate');\n\n      search.init();\n\n      input(searchInput, 'peace');\n      click(submitInput);\n\n      expect(search.validate).toHaveBeenCalledWith('peace');\n    });\n\n    // ...\n  });\n});\n```\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Know your testing framework API\n\nThe API documentation of the testing framework/library should be your bedside book!\n\nHaving a good knowledge of the API can help you in reducing the size/complexity of your test code and, in general, help you during development. A simple example:\n\n**:(**\n\n```js\nit('should call a method with the proper arguments', () =\u003e {\n  const foo = {\n    bar: jasmine.createSpy(),\n    baz: jasmine.createSpy()\n  };\n\n  foo.bar('qux');\n\n  expect(foo.bar).toHaveBeenCalled();\n  expect(foo.bar.calls.argsFor(0)).toEqual(['qux']);\n});\n\n/*it('should do more but not now', () =\u003e {\n});\n\nit('should do much more but not now', () =\u003e {\n});*/\n```\n\n**:)**\n\n```js\nfit('should call once a method with the proper arguments', () =\u003e {\n  const foo = jasmine.createSpyObj('foo', ['bar', 'baz']);\n\n  foo.bar('baz');\n\n  expect(foo.bar).toHaveBeenCalledWith('baz');\n});\n\nit('should do something else but not now', () =\u003e {\n});\n\nit('should do something else but not now', () =\u003e {\n});\n```\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n#### Note\n\nThe handy `fit` function used in the example above allows you to execute only one test without having to comment out all the tests below. `fdescribe` does the same for test suites. This could help save a lot of time when developing.\n\nMore information on the [Jasmine website](http://jasmine.github.io).\n\n\n\n### Don't test multiple concerns in the same test\n\nIf a method has several end results, each one should be tested separately. Whenever a bug occurs, it will help you locate the source of the problem.\n\n**:(**\n\n```js\nit('should send the profile data to the server and update the profile view properly', () =\u003e {\n  // expect(...)to(...);\n  // expect(...)to(...);\n});\n```\n\n**:)**\n\n```js\nit('should send the profile data to the server', () =\u003e {\n  // expect(...)to(...);\n});\n\nit('should update the profile view properly', () =\u003e {\n  // expect(...)to(...);\n});\n```\n\nBeware that writing \"AND\" or \"OR\" when naming your test smells bad...\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Cover the general case and the edge cases\n\n\"Strange behaviour\" usually happens at the edges... Remember that your tests can be the live documentation of your code.\n\n**:(**\n\n```js\nit('should properly calculate a RPN expression', () =\u003e {\n  const result = RPN('5 1 2 + 4 * - 10 /');\n  expect(result).toBe(-0.7);\n});\n```\n\n**:)**\n\n```js\ndescribe('The RPN expression evaluator', () =\u003e {\n  it('should return null when the expression is an empty string', () =\u003e {\n    const result = RPN('');\n    expect(result).toBeNull();\n  });\n\n  it('should return the same value when the expression holds a single value', () =\u003e {\n    const result = RPN('42');\n    expect(result).toBe(42);\n  });\n\n  it('should properly calculate an expression', () =\u003e {\n    const result = RPN('5 1 2 + 4 * - 10 /');\n    expect(result).toBe(-0.7);\n  });\n\n  it('should throw an error whenever an invalid expression is passed', () =\u003e {\n    const compute = () =\u003e RPN('1 + - 1');\n    expect(compute).toThrow();\n  });\n});\n```\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### When applying TDD, always start by writing the simplest failing test\n\n**:(**\n\n```js\nit('should suppress all chars that appear multiple times', () =\u003e {\n  expect(keepUniqueChars('Hello Fostonic !!')).toBe('HeFstnic');\n});\n```\n\n**:)**\n\n```js\nit('should return an empty string when passed an empty string', () =\u003e {\n  expect(keepUniqueChars('')).toBe('');\n});\n```\n\nFrom there, start building the functionalities incrementally.\n\n\n\n### When applying TDD, always make small steps in each test-first cycle\n\nBuild your tests suite from the simple case to the more complex ones. Keep in mind the incremental design. Deliver software fast, incrementally, and in short iterations.\n\n**:(**\n\n```js\nit('should return null when the expression is an empty string', () =\u003e {\n  const result = RPN('');\n  expect(result).toBeNull();\n});\n\nit('should properly calculate a RPN expression', () =\u003e {\n  const result = RPN('5 1 2 + 4 * - 10 /');\n  expect(result).toBe(-0.7);\n});\n```\n\n**:)**\n\n```js\ndescribe('The RPN expression evaluator', () =\u003e {\n  it('should return null when the expression is an empty string', () =\u003e {\n    const result = RPN('');\n    expect(result).toBeNull();\n  });\n\n  it('should return the same value when the expression holds a single value', () =\u003e {\n    const result = RPN('42');\n    expect(result).toBe(42);\n  });\n\n  describe('Additions-only expressions', () =\u003e {\n    it('should properly calculate a simple addition', () =\u003e {\n      const result = RPN('41 1 +');\n      expect(result).toBe(42);\n    });\n\n    it('should properly calculate a complex addition', () =\u003e {\n      const result = RPN('2 9 + 15 3 + + 7 6 + +');\n      expect(result).toBe(42);\n    });\n  });\n\n  // ...\n\n  describe('Complex expressions', () =\u003e {\n    it('should properly calculate an expression containing all 4 operators', () =\u003e {\n      const result = RPN('5 1 2 + 4 * - 10 /');\n      expect(result).toBe(-0.7);\n    });\n  });\n});\n```\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Test the behaviour, not the internal implementation\n\n**:(**\n\n```js\nit('should add a user in memory', () =\u003e {\n  userManager.addUser('Dr. Falker', 'Joshua');\n\n  expect(userManager._users[0].name).toBe('Dr. Falker');\n  expect(userManager._users[0].password).toBe('Joshua');\n});\n```\n\nA better approach is to test at the same level of the API:\n\n**:)**\n\n```js\nit('should add a user in memory', () =\u003e {\n  userManager.addUser('Dr. Falker', 'Joshua');\n\n  expect(userManager.loginUser('Dr. Falker', 'Joshua')).toBe(true);\n});\n```\n\nPro:\n\n+ Changing the internal implementation of a class/object will not necessarily force you to refactor the tests\n\nCon:\n\n+ If a test is failing, we might have to debug to know which part of the code needs to be fixed\n\nHere, a balance has to be found, unit-testing some key parts can be beneficial.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Don't mock everything\n\n**:(**\n\n```js\ndescribe('when the user has already visited the page', () =\u003e {\n  // storage.getItem('page-visited', '1') === '1'\n  describe('when the survey is not disabled', () =\u003e {\n    // storage.getItem('survey-disabled') === null\n    it('should display the survey', () =\u003e {\n      const storage = jasmine.createSpyObj('storage', ['setItem', 'getItem']);\n      storage.getItem.and.returnValue('1'); // ouch.\n\n      const surveyManager = new SurveyManager(storage);\n      spyOn(surveyManager, 'display');\n\n      surveyManager.start();\n\n      expect(surveyManager.display).toHaveBeenCalled();\n    });\n  });\n\n  // ...\n});\n```\n\nThis test fails, because the survey is considered disabled. Let's fix this:\n\n**:)**\n\n```js\ndescribe('when the user has already visited the page', () =\u003e {\n  // storage.getItem('page-visited', '1') === '1'\n  describe('when the survey is not disabled', () =\u003e {\n    // storage.getItem('survey-disabled') === null\n    it('should display the survey', () =\u003e {\n      const storage = jasmine.createSpyObj('storage', ['setItem', 'getItem']);\n      storage.getItem.and.callFake(key =\u003e {\n        switch (key) {\n          case 'page-visited':\n            return '1';\n\n          case 'survey-disabled':\n            return null;\n        }\n\n        return null;\n      }); // ouch.\n\n      const surveyManager = new SurveyManager(storage);\n      spyOn(surveyManager, 'display');\n\n      surveyManager.start();\n\n      expect(surveyManager.display).toHaveBeenCalled();\n    });\n  });\n\n  // ...\n});\n```\n\nThis will work... but needs a lot of code. Let's try a simpler approach:\n\n**:(**\n\n```js\ndescribe('when the user has already visited the page', () =\u003e {\n  // storage.getItem('page-visited', '1') === '1'\n  describe('when the survey is not disabled', () =\u003e {\n    // storage.getItem('survey-disabled') === null\n    it('should display the survey', () =\u003e {\n      const storage = window.localStorage; // ouch.\n      storage.setItem('page-visited', '1');\n\n      const surveyManager = new SurveyManager();\n      spyOn(surveyManager, 'display');\n\n      surveyManager.start();\n\n      expect(surveyManager.display).toHaveBeenCalled();\n    });\n  });\n\n  // ...\n});\n```\n\nWe created a permanent storage of data. What happens if we do not properly clean it?\nWe might affect the other tests. Let's fix this:\n\n**:) :)**\n\n```js\ndescribe('when the user has already visited the page', () =\u003e {\n  // storage.getItem('page-visited', '1') === '1'\n  describe('when the survey is not disabled', () =\u003e {\n    // storage.getItem('survey-disabled') === null\n    it('should display the survey', () =\u003e {\n      const storage = new MemoryStorage(); // see https://github.com/tatsuyaoiw/webstorage\n      storage.setItem('page-visited', '1');\n\n      const surveyManager = new SurveyManager(storage);\n      spyOn(surveyManager, 'display');\n\n      surveyManager.start();\n\n      expect(surveyManager.display).toHaveBeenCalled();\n    });\n  });\n});\n```\n\nThe `MemoryStorage` used here does not persist data. Nice and easy, with no side effects.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n#### Takeaway\n\nThe idea to keep in mind is that *dependencies can still be \"real\" objects*. Don't mock everything because you can.\nIn particular, consider using the \"real\" version of the objects if:\n\n+ it leads to a simple, nice and easy tests setup\n+ it does not create a shared state between the tests, causing unexpected side effects\n+ the code being tested does not make AJAX requests, API calls or browser page reloads\n+ the speed of execution of the tests stays *within the limits you fixed*\n\n\n\n### Create new tests for every defect\n\nWhenever a bug is found, create a test that replicates the problem **before touching any code**. From there, you can apply TDD as usual to fix it.\n\n\n\n### Don't write unit tests for complex user interactions\n\nExamples of complex user interactions:\n\n+ Filling a form, drag and dropping some items then submitting the form\n+ Clicking a tab, clicking an image thumbnail then navigating through a gallery of images previously loaded from a database\n+ (...)\n\nThese interactions might involve many units of work and should be handled at a higher level by **functional tests**. They will take more time to execute. They could be flaky (false negatives) and they need debugging whenever a failure is reported.\n\nFor functional testing, consider using a test automation framework ([Selenium](http://docs.seleniumhq.org/), ...) or QA manual testing.\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n\n### Test simple user actions\n\nExample of simple user actions:\n\n+ Clicking on a link that toggles the visibility of a DOM element\n+ Submitting a form that triggers the form validation\n+ (...)\n\nThese actions can be easily tested **by simulating DOM events**, for example:\n\n```js\ndescribe('clicking on the \"Preview profile\" link', () =\u003e {\n  it('should show the profile preview if it is hidden', () =\u003e {\n    const previewLink = document.createElement('a');\n    const profileModule = createProfileModule({ previewLink, previewIsVisible: false });\n\n    spyOn(profileModule, 'showPreview');\n\n    click(previewLink);\n\n    expect(profileModule.showPreview).toHaveBeenCalled();\n  });\n\n  it('should hide the profile preview if it is displayed', () =\u003e {\n    const previewLink = document.createElement('a');\n    const profileModule = createProfileModule({ previewLink, previewIsVisible: true });\n\n    spyOn(profileModule, 'hidePreview');\n\n    click(previewLink);\n\n    expect(profileModule.hidePreview).toHaveBeenCalled();\n  });\n});\n```\n\nNote how simple the test is because the UI (DOM) layer does not mix with the business logic layer:\n\n+ a \"click\" event occurs\n+ a public method is called\n\nThe next step could be to test the business logic implemented in \"showPreview()\" or \"hidePreview()\".\n\n### Libraries\n\n+ Jasmine: https://jasmine.github.io/\n+ Jest: https://jestjs.io/\n+ Mocha: https://mochajs.org/\n+ Tape: https://github.com/substack/tape\n\n\u003cdiv align=\"right\"\u003e\n    \u003cb\u003e\u003ca href=\"#\"\u003e↥ back to top\u003c/a\u003e\u003c/b\u003e\n\u003c/div\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearning-zone%2Fjavascript-unit-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flearning-zone%2Fjavascript-unit-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearning-zone%2Fjavascript-unit-testing/lists"}