I’ve learned 4 things this past 6 months doing peer reviews on many teams’ code in a large Angular code base.
- Pure Functions are not an easy sell
- Avoiding Dependencies in code is not obvious
- Anti-Mock crowd unfortunately is more nuanced
- Acceptance Test Ops continue to be challenging
Pure Functions Not An Easy Sell
The joy of unit testing pure functions over dependencies is not an easy sell because developers have to think about how to separate their business logic from side-effects, and that’s challenging.
Jason Gorman covers that in his video
Avoiding Dependencies in Code is Not Obvious
The “obviousness” of not wanting dependencies in your code, and wanting a class method/function to take inputs and have an output is not at all obvious.
Many developers see zero problems, or just think “this is way things are supposed to be” with lots of mocks/spies being setup, methods/functions that return no value, and then state is verified after the fact, much of which has intimate knowledge of what goes in within the abstraction. Returning values from class methods is seen as an edge case, and void is common.
Scott Wlaschin covers this in this Moving IO to the edges of your app video
Anti-Mock Crowd Unfortunately is More Nuanced
The many cases against mocks are not all true depending upon what language and mocking framework you’re talking about, which was a huge surprise to me. Despite many in the OOP community leveraging as a last resort, or many in the FP community not using them at all, developers are still making mocks easier to work with.
Joe Blu covers in Go how mocks cause the tests to have to be updated if you add something to the base module which happens in OOP languages as well. Turns out this isn’t true anymore in some mocking frameworks. Jest for example, leveraging mutation and types, can modify an existing class via jest.spyOn and other methods. This allows you to update the class and it won’t break, or force you to update any tests.
Thankfully for my sanity, Joe’s 2nd point of fragile tests still stands. OOP developers in particular are affected by this, no fault of their own, and Angular in particular. This is exacerbated by inject making it easy to add more and more dependencies to classes, encapsulate side-effects throughout the code, and yet these abstractions abstract nothing because the tests are well aware of the implementation details.
Acceptance Test Ops Continue to be Challenging
Many developers do not like, nor see the value of Acceptance tests, and prefer mocks for their speed in validating side-effects despite the test brittleness, lack of abstraction, and thus higher coupling costs.
This is because many Cypress or Playwright tests are wrapped in slow local and remote Continuous Integration build systems that shorten the feedback loop enough that developers tap out.
Tim Cochran covers this is his Maximizing Developer effectiveness article.
The other problem is 3rd party libraries and platforms make it difficult to validate they are working as intended. Meaning they took the “Don’t Test What You Don’t Own” to heart, and assumed developers leveraging these libraries/platforms would never test their stuff. An example is analytics. If a developer writes a feature to log a user saw a particular screen, how then would they write an acceptance test to validate they sent you, the analytics platform, the correct message?
If you don’t expose an easily wrap-able REST call that Cypress can wrap, you don’t provide a way to tap into a global variable you set to ensure a message was sent, nor do you provide logs that can be accessed via an API (e.g. AWS CloudWatch logs/Splunk), then your only recourse you’ve provided to developer teams is manual testing, and that is not conducive to good software delivery.
Top comments (1)
Hi Jesse Warden,
Top, very nice and helpful !
Thanks for sharing.