Intro To JavaScript Unit Testing
Unit testing is an integral part of a well-maintained javaScript application. Having common test cases covered can save an organization time and money while also preventing unnecessary developer stress. Thanks to modern testing frameworks like Jest and Cypress, unit testing is much less time-consuming and most importantly, fun. In this post, I’ll go over some of the benefits of unit testing and a high-level overview of how these tests are written.
Benefits of Unit Testing
You might be wondering what all the fuss over unit testing is if integration and functional testing are planned. Sounds like a bit of overkill, right? It can be hard to fully appreciate the benefits of unit testing until you realize that you’ve made a small change that mysteriously breaks another part of your application. If there are no unit tests in place, you have to manually test your app to find the issue. That would be a nightmare!
Luckily, this situation can easily be avoided by having unit test cases to find those app-breaking changes ahead of time. Having unit testing in place means that introducing a new feature or making changes to an existing one will be less daunting. Running unit tests to make sure existing functionality works as expected gives you the extra confidence that your application won’t blow up in production.
Unit tests help find bugs early in the development life cycle, and also help keep code quality high. When developers know they have to write unit tests, they tend to spend more time thinking about the implementation and approach. Code that is easier to understand/read makes onboarding new team members more pleasant as well. Another major benefit of unit testing is being able to pivot with the business quickly to add or remove functionality when necessary.
Unit Testing Overview And Structure
Today there are many different testing frameworks available. Some like Jest, Jasmine, TestCafe, and Cypress come with everything you will need out of the box. Others will require a combination of libraries and frameworks to accomplish similar functionality. A common combination of tools would be mocha + chai + sinon.
Regardless of the framework, proper organization of your tests will help you to arrange your test suites in a readable and scalable way. Unit tests are usually organized in BDD (behavior-driven development) and consist of describe() and it() functions to logically group your tests.
Example1:
describe(‘calculator’, function() {// describes a module with nested “describe” functionsdescribe(‘add’, function() {// specify the expected behaviorit(‘should add 2 numbers’, function() {//Use assertion functions to test the expected behavior…})})})
A common way to arrange each test within the structure above is using the Arrange-Act-Assert pattern. This arrangement is a way to separate your unit test into three sections making it easy to follow and understand.
Arrange: This section is where you initialize objects and set the value of data and mocks to be used by your test if needed.
Act: This section is where the function you are trying to test is invoked.
Assert: This section is where you verify that the function behaves as expected, using helper functions/matchers provided by the testing framework that you’re using. Each framework has its own built-in helper functions or matchers that you can leverage. To learn more check out the documentation for whatever framework you choose. https://jestjs.io/docs/using-matchers
Example2:
it(‘should do something’, function() {//arrange…var dummyData = { foo: ‘bar’ };var expected = ‘the result we want’;//act…var result = functionUnderTest(dummyData);//assert…expect(result).to.equal(expected);});
Example3:
import user from ‘./user’ // using jest for this exampledescribe(‘user’, () => {// before each testbeforeEach(() => {// spy on the “isValid” function on the “user” objectjest.spyOn(user, ‘isValid’);})// after each testafterEach(() => {// remove the mock from the “isValid” functionuser.isValid.mockRestore();})it(‘is validated for valid users’, () => {user.isValid.mockReturnValue(true)// Testing someFn with user where user.isValid() returns trueassert(validate(user))})it(‘does not validated for invalid users’, () => {user.isValid.mockReturnValue(false)// Testing someFn with user where user.isValid() returns trueassert(!validate(user))})})
Conclusion
Unit testing is often overlooked and may be considered boring, but when done correctly, these tests can save you time and money, not to mention headaches down the road. There is much more to learn about unit testing that isn’t covered here, including code coverage, snapshots, and even auto-generated HTML reports, so I encourage you to do more research and test away!
Resources:
https://codeutopia.net/blog/2017/05/15/quick-javascript-testing-tip-how-to-structure-your-tests/
https://www.guru99.com/javascript-unit-testing-frameworks.html
https://dev.to/dstrekelj/how-to-write-unit-tests-in-javascript-with-jest-2e83