└── README.md /README.md: -------------------------------------------------------------------------------- 1 | A Guide to Javascript (and not only) Unit Testing 2 | ================================= 3 | 4 | A document that was made to put together some of the knowledge regarding unit testing in JavaScript. Feel free to use it for your needs or to edit. 5 | 6 | ### The goal of these guidelines is to make your tests: 7 | 8 | * Readable 9 | * Maintainable 10 | * Trustworthy 11 | 12 | ### Unit tests are not for finding bugs!!! 13 | 14 | Goal | Strongest technique 15 | ------------ | ------------- 16 | Finding bugs (things that don’t work as you want them to)| Manual testing (sometimes also automated integration tests) 17 | Detecting regressions (things that used to work but have unexpectedly stopped working| Automated integration tests (sometimes also manual testing, though time-consuming) 18 | Designing software components robustly| Unit testing (within the TDD process) 19 | 20 | ### Unit Testing Best Practices 21 | 1. **Always Write Isolated Test Cases** 22 | The order of execution has to be independent between test cases. 23 | 2. **Test One Thing Only in One Test Case** 24 | If a method has several end results, each one should be tested separately. 25 | Whenever a bug occurs, it will help you locate the source of the problem. 26 | **:(** 27 | ```javascript 28 | it('should send the data to the server and update the view properly', () => 29 | { 30 | // expect(...)to(...); 31 | // expect(...)to(...); 32 | }); 33 | ``` 34 | **:)** 35 | ```javascript 36 | it('should send the data to the server', () => { 37 | // expect(...)to(...); 38 | }); 39 | 40 | it('should update the view properly', () => { 41 | // expect(...)to(...); 42 | }); 43 | ``` 44 | Be aware that writing "AND" or "OR" when naming your test smells bad... 45 | 46 | 3. **Describe your tests properly** 47 | This helps to avoid comments and increases the maintainability and in the case a test fails you know faster what functionality has been broken. Keep in mind that someone else will read it too. 48 | Tests can be the live documentation of the code. 49 | 50 | In order to help you write test names properly, you can use the "unit of work - scenario/context - expected behaviour" pattern: 51 | 52 | ```javascript 53 | describe('[unit of work]', () => { 54 | it('should [expected behaviour] when [scenario/context]', () => { 55 | }); 56 | }); 57 | ``` 58 | Or whenever you have many tests that follow the same scenario or are related to the same context: 59 | 60 | ```javascript 61 | describe('[unit of work]', () => { 62 | describe('when [scenario/context]', () => { 63 | it('should [expected behaviour]', () => { 64 | }); 65 | }); 66 | }); 67 | ``` 68 | 69 | ```javascript 70 | describe("binding dependencies", () => { 71 | it("should remove handler when element was removed", () => { 72 | // code 73 | }); 74 | 75 | it("should add handler when element was added", () => { 76 | // code 77 | }) 78 | }) 79 | ``` 80 | In Angular use describe statement to separate unit tests, template shallow tests and integration tests: 81 | 82 | ```javascript 83 | describe('unit tests', () => { 84 | describe('[unit of work]', () => { 85 | describe('when [scenario/context]', () => { 86 | it('should [expected behaviour]', () => { 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('template shallow tests', () => { 93 | describe('[unit of work]', () => { 94 | describe('when [scenario/context]', () => { 95 | it('should [expected behaviour]', () => { 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('integration tests', () => { 102 | describe('[unit of work]', () => { 103 | describe('when [scenario/context]', () => { 104 | it('should [expected behaviour]', () => { 105 | }); 106 | }); 107 | }); 108 | }); 109 | ``` 110 | 111 | 4. **Use the Arrange-Act-Assert Style** 112 | 5. **Measure Code Coverage to Find Missing Test Cases** 113 | Use [wallaby app](http://wallabyjs.com/app/#/tests). 114 | 6. **Don't Forget to Refactor the Test Code** 115 | Also maintain your test code (especially when after refactoring the code under test). 116 | 7. **Limit Use of Mocks** 117 | In some cases absolutely necessary, but with better design stubs should be enough. 118 | *Mocks vs stubs* 119 | *Mock* objects are used to define *expectations* i.e: In this scenario I expect method A() to be called with such and such parameters.Mocks record and verify such expectations. 120 | *Stubs*, on the other hand have a different purpose: they do not record or verify expectations, but rather allow us to *“replace”* the behavior, state of the “fake” object in order to utilize a test scenario. 121 | 8. **Avoud logic in tests** 122 | **:(** 123 | ```javascript 124 | it('should properly sanitize strings', () => { 125 | let result; 126 | const testValues = { 127 | 'Avion' : 'Avi' + String.fromCharCode(243) + 'n', 128 | 'The-space' : 'The space', 129 | 'Weird-chars-' : 'Weird chars!!', 130 | 'file-name.zip' : 'file name.zip', 131 | 'my-name.zip' : 'my.name.zip' 132 | }; 133 | 134 | for (result in testValues) { 135 | expect( sanitizeString(testValues[result]) ).toEqual(result); 136 | } 137 | }); 138 | ``` 139 | **:)** 140 | ```javascript 141 | it('should sanitize a string containing non-ASCII chars', () => { 142 | expect( sanitizeString('Avi'+String.fromCharCode(243)+'n') ).toEqual('Avion'); 143 | }); 144 | 145 | it('should sanitize a string containing spaces', () => { 146 | expect( sanitizeString('The space') ).toEqual('The-space'); 147 | }); 148 | 149 | it('should sanitize a string containing exclamation signs', () => { 150 | expect( sanitizeString('Weird chars!!') ).toEqual('Weird-chars-'); 151 | }); 152 | 153 | it('should sanitize a filename containing spaces', () => { 154 | expect( sanitizeString('file name.zip') ).toEqual('file-name.zip'); 155 | }); 156 | 157 | it('should sanitize a filename containing more than one dot', () => { 158 | expect( sanitizeString('my.name.zip') ).toEqual('my-name.zip'); 159 | }); 160 | ``` 161 | 9. **Don't write unnecessary expectations** 162 | Remember, 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. 163 | 10. **Cover the general case and the edge cases** 164 | "Strange behaviour" usually happens at the edges... 165 | Remember that your tests can be the live documentation of your code. 166 | 11. **Test the behaviour, not the internal implementation** 167 | **:(** 168 | 169 | ```javascript 170 | it('should add a user in memory', () => { 171 | userManager.addUser('Dr. Falker', 'Joshua'); 172 | 173 | expect(userManager._users[0].name).toBe('Dr. Falker'); 174 | expect(userManager._users[0].password).toBe('Joshua'); 175 | }); 176 | ``` 177 | A better approach is to test at the same level of the API: 178 | 179 | **:)** 180 | 181 | ```javascript 182 | it('should add a user in memory', () => { 183 | userManager.addUser('Dr. Falker', 'Joshua'); 184 | 185 | expect(userManager.loginUser('Dr. Falker', 'Joshua')).toBe(true); 186 | }); 187 | ``` 188 | *Advantage*: 189 | Changing the internal implementation of a class/object will not necessarily force you to 190 | refactor the tests 191 | *Disadvantage*: 192 | If a test is failing, we might have to debug to know which part of the code needs to be fixed. 193 | Here, a balance has to be found, unit-testing some key parts can be beneficial. 194 | 12. **Create new tests for every defect** 195 | Whenever 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. 196 | 197 | ### Current problems in AngularJS 198 | 199 | 1. Only methods that are presented in the interfaces should be tested or mocked. The only 200 | exception now are controllers in AngularJS and their bindToController properties. 201 | ```javascript 202 | bindToController: { 203 | isOpen: "=", 204 | viewModel: "=" 205 | } 206 | ``` 207 | 2. Instead of first defining items and later testing them. Do define and testing together. 208 | 3. Wiseman said: “If you need to debug unit test then something is wrong with it’s unitness.” 209 | Don't write too big classes or functions. 210 | 211 | 212 | ### Style 213 | 1. Alignment 214 | 215 | ```javascript 216 | It("should do something", () => 217 | { 218 | //code 219 | }); 220 | ``` 221 | 222 | --------------------------------------------------------------------------------