Whether we use a testing framework like Mocha or Jasmine that works with Node, or we run DOM-dependent tests in a headless browser like PhantomJS, we have better ways to unit test JavaScript than ever before. However, this doesn't mean that the code we want to test is as easy as our tools! Organizing and writing easily testable code takes some effort and planning, but we've found some patterns inspired by functional programming that can help us avoid those pitfalls when we need to test our code. In this post, we'll look at some useful tips and patterns to help us write testable code in JavaScript. Keep business logic and display logic separate One of the main jobs of a JavaScript-based browser application is to listen for DOM events triggered by the end user, and then respond to the user by running some business logic and displaying the results on the page. Where you set up a DOM event listener, it is sometimes tempting to write an anonymous function to do all this work. The problem with this is that in order to test the anonymous function, you have to simulate DOM events. This not only increases the number of lines of code, but also increases the time it takes to run the test. Instead, write a named function and pass it to the event handler. This way, you can write tests directly against the named function without having to trigger a fake DOM event. This doesn't just apply to the DOM. Many APIs in browsers and Node are designed to fire and listen for events, or wait for other types of asynchronous work to complete. As a rule of thumb, if you're writing a lot of anonymous callbacks, your code probably isn't going to be easy to test.
Use callbacks or promises for asynchronous code In the sample code above, our refactored function fetchThings runs an AJAX request, doing most of the work asynchronously. This means we can't run the function and test that it does what we expect, because we don't know when it will finish. The most common way to solve this problem is to pass a callback function as a parameter to the function as an asynchronous call. Then, in your unit test, you can run some assertions in the passed callback function. Another common and increasingly popular way to organize asynchronous code is to use the Promise API. Fortunately, $.ajax and most other jQuery asynchronous functions already return Promise objects, so it already covers most common use cases.
Avoid side effects Write functions that take arguments and return values that depend only on those arguments, like passing numbers into a math formula and getting a result. If your function depends on some external state (such as attributes of a class instance or the contents of some file), then you have to set up that state before testing the function, which requires more setup in the test case. You have to assume that the code you're running won't modify the same state. Likewise, you should avoid writing functions that modify external state, such as writing to a file or saving data to a database. This will prevent side effects that will affect your ability to test other code. In general, it is best to keep side effects within your code, so that the "surface area" is as small as possible. For classes and object instances, side effects of class methods should be limited to the scope of the class instance being tested.
Using Dependency Injection There is a common pattern in functions that can be used to reduce the use of external state, which is dependency injection - passing all external requirements of the function to the function through function parameters.
One of the main benefits of using dependency injection is that you can pass in mock objects in your unit tests so that they don’t cause real side effects (in this case, updating a database row) and you only need to assert that your mock objects behave in the expected way. Give each function a unique purpose Breaking down a long function into a series of small, single-responsibility functions makes it easier to test whether each function is correct, rather than hoping that one large function does everything correctly before returning a result. In functional programming, the act of stringing together several functions with a single responsibility is called composition. Underscore.js even has a function called _.compose that strings together a list of functions, passing each function’s result as input to the next.
Do not change the parameters In JavaScript, arrays and objects are passed by reference, not by value, so they are mutable. This means that when you pass an object or array as a parameter to a function, both your code and the function that uses the object or array you passed have the ability to modify the same array or object in memory. This means that when you test your own code, you have to trust that none of the functions you call modify your objects. Every time you add some new code that can modify the same object, it becomes increasingly difficult to keep track of what the objects should look like, and therefore more difficult to test them.
Instead, when you have a function that needs to use an object or array, you should treat the object or array as if it were read-only in your code. You can create new objects or arrays as needed and then align the fills. Alternatively, use Underscore or Lodash to make a copy of the passed object or array and then align the operation. Even better, use some tools like Immutable.js to create read-only data structures.
Write tests before coding The process of writing unit tests before writing code is called test-driven development (TDD). A large number of developers find TDD very useful. By writing the tests first, you force yourself to think about the API you are exposing from the perspective of the developer who will use your code. It also helps you ensure that you only write enough code to satisfy the test cases, and do not "over-engineer" the solution, introducing unnecessary complexity. In practice, TDD as a discipline can be difficult to cover for all code changes, but when it seems worthwhile, it is a great way to ensure that all your code is testable. Summarize We all know there are some easy “gotchas” when writing and testing complex JavaScript applications, but I hope that by following these tips and reminders and keeping our code as simple and functional as possible, we can keep test coverage high and overall code complexity low! |
<<: Understand the real 『REM』 mobile screen adaptation
>>: [Umeng+] Li Danfeng: Insight into the business secrets of big data from user behavior data
Welcome to the 44th issue of the Nature Trumpet c...
Recently, the cat in the TV series "The Mist...
Source code introduction: Customize the digital p...
Learning from user feedback is a big or small thi...
Many residents have reported that their tap water...
Products and operations are inseparable, and good...
"I still don't understand what Werewolf ...
In our lives, the Internet has occupied more and ...
In the many marketing copy screen-sweeping incide...
Guangdiantong is one of Tencent’s two major infor...
Recently, Lancôme's Spring Festival Garden Pa...
In marketing promotion , if target users are simp...
Talking about hot topics and leveraging trends, i...
After the conquest of 2014 and the release of fla...
Written by | Carlo Let's first popularize the...