TypeScript guides
...
Testing
Writing Reasonable & Testable Code
6 min
writing tests for code is one thing, but writing testable code is another! testable code comes from abstracting control flows and operations on data with functions in order to avoid side effects and reduce, or at least have better control over, the mutation of state in your application in the functional programming (fp) paradigm, pure functions are functions which do not mutate any existing state of any scope since we are not in a fully functional paradigm, a focus on these qualities of functions can be priceless stateless https //en wikipedia org/wiki/state (computer science) functions the function itself has no memory of the past referential transparency https //en wikipedia org/wiki/referential transparency the function operates on its parameters, and nothing else (no global state access, etc) these types of functions may (arguably) mutate parameter state, but may only operate on the given parameters state residing outside of the scope of a stateless function should never be depended on or mutated this will ensure that the function holds no inherent state of its own, and therefore will exhibit the behavior of being referentially transparent and idempotent https //en wikipedia org/wiki/idempotence idempotency is the quality of a function that can be executed several times without changing its output for a specific input idempotent functions can be thought of as mappings from one input to one output all of this combined makes functions extremely simple to reason about, very reusable, and easy to test! even if it takes a little bit more effort to write functions in this way at first, it will save us a lot of time, energy and stress in the long run example here is an example of a function that does not exhibit referential transparency and it is not stateless it is also not idempotent same input, different output! let y = 3 const somefunction = x => { x += y y++ return x } somefunction(3) // => 6 somefunction(3) // => 7 somefunction(3) // => 8 this first example is not good for reasonable code and will be very difficult to predict the same function written as a stateless and idempotent function with referential transparency would look like this const somefunction = (data, x) => { x += data y data y++ return x } somefunction({ y 3 }, 3) // => 6 somefunction({ y 3 }, 3) // => 6 somefunction({ y 3 }, 3) // => 6 in contrast, this second function holds no inherent state of its own does not operate on any data that was not passed into the function as explicit arguments is idempotent same input, same output the function is now very reasonable and easy to predict code composition / decomposition we must now capture the process of decomposing a program into smaller pieces that are more reusable, more reliable, and easier to understand then we can combine each individual piece to form an entire program that is easier to reason about as a whole fp tends to follow this fundamental principle fp falls under the umbrella of declarative programming paradigms it expresses a set of operations without revealing how they’re implemented or how data flows through them unlike imperative programming, declarative programming separates program description from evaluation it focuses on the use of expressions to describe what the logic of a program is without necessarily specifying its control flow or state change these two paradigms can be used together to form powerful and extremely testable functions and compositions which support a sturdy codebase write functions imperatively, then compose them together declaratively! example using the previous unit/integration test examples, lets see what the algorithm would look like when written imperatively const algorithm = x => { x += 2 // first, add two x = 3 // then, multiply by three x /= 2 // finally, divide by two return x } lets rewrite the same function, as demonstrated before, but this time in a declarative way const addtwo = x => x + 2 const multthree = x => x 3 const halve = x => x / 2 const algorithm = pipe(addtwo, multthree, halve) // the `pipe` function is standard in fp algorithm(4) // => 9 as you can see, the imperative function has no reusable parts, but the declarative version does! this is a simple example but, in larger scale functions and systems, this simple distinction can be a powerful tool in writing reasonable, testable and reusable code bonus the code is now self documenting no need for comments, just pure and self descriptive functions!