I really enjoyed this article and spoke to a lot of the lived experience I've had in over abstracted codebases that had tests incredibly coupled to implementation.
I think this title could maybe be rewritten as "Unit Testing that Requires Mocks is Overrated".
Unit testing something that has no (or very simple) dependencies is great. For example:
- some kind of encryption that takes in a string as a key
- serialization that expects the same output as the input passed in
- a transform function that takes in one type and expects a different type out
As soon as you rely on a DB, file system, etc... you're probably better off with an e2e test.
At the end of the day, it comes down to data contracts. This could be the functions a package exposes or the GQL/REST/gRPC/whatever API. That is the most important thing to not break the behavior of. Write good tests that target the external facing data contracts and treat implementation like a black box and you'll be empowered to do the important things that tests should enable like refactoring, reworking abstractions that may have made sense at one point, but no longer do, and let your codebase evolve as you learn.
Tests that are a barrier to reworking internal abstractions are not good tests.
I've been getting paid to write JS/TS for about 10 years. I think I've used the `class` keyword less than 5 times in the last 5 years and feel better off without it. I've found that a more functional style leads to better separation of state/data from any logic you have to write, allowing for a better testing experience and an easier time refactoring. Plus it means I have to think way less about what `this` means.
At the end of the day, features of languages are just tools, and sometimes they fit the problem you're trying to solve. I think it's also important to decide what tools to use in the context of what experience you and your team already have.
Also, it's hard to have small .js files when you use classes, and I love small files. I think Go made a great design decision to allow functions on structs to be implemented in separate files.
I once heard someone tell me that good OOP ends up looking very functional. I've taken that to heart, and I believe the code I write is better as a result.
I do use the `class` keyword in my js, but I'm also on a team of C++/Ruby programmers who are forced to write js, so the more I can do to make it more familiar, the better. As you said, it's just a tool. Some people like nails and some people like screws; there are objective advantages to each, but either one will hold a house together.
> I once heard someone tell me that good OOP ends up looking very functional.
Immutability and thinking about if a class should really be stateful are the major wins.
I was recently looking over some code that essentially just did transformations, but all the inputs and outputs were member variables, so it took some effort to track what creates what and what depends on what. The members also had empty defaults, so if you mix up the order you call the functions in, the code still appears to work. It wasn't "wrong;" it was hard to follow and error-prone.
Using 'classes' doesn't imply OO. They're a perfectly good way to organize data, at very least for data objects in TS.
It's fine to want to go functionally heavy, but if you're not using classes - which are the foundation of 'types' - then why bother with 'type'script? (Unless you mean to say 'interface'? Which is obviously not 'class' but implies the same usage of OO-founded keywords)
In TypeScript classes are in no way foundational. At the type level they are roughly implemented as a combination of two types: the instance type and the "newable" constructor function type.
I think this title could maybe be rewritten as "Unit Testing that Requires Mocks is Overrated".
Unit testing something that has no (or very simple) dependencies is great. For example: - some kind of encryption that takes in a string as a key - serialization that expects the same output as the input passed in - a transform function that takes in one type and expects a different type out
As soon as you rely on a DB, file system, etc... you're probably better off with an e2e test.
At the end of the day, it comes down to data contracts. This could be the functions a package exposes or the GQL/REST/gRPC/whatever API. That is the most important thing to not break the behavior of. Write good tests that target the external facing data contracts and treat implementation like a black box and you'll be empowered to do the important things that tests should enable like refactoring, reworking abstractions that may have made sense at one point, but no longer do, and let your codebase evolve as you learn.
Tests that are a barrier to reworking internal abstractions are not good tests.