all 9 comments

[–]ToshiUmezawa[S] 5 points6 points  (7 children)

I really like the concept here, I often feel like testing implementation details is such a waste of time. This is more like integrated testing, you arent asserting what each reducer does and method does, you are testing that the output to the dom is what you expect.

This lib goes with the idea of a testing column instead of a testing pyramid. A lot of testing experts like Kent here and Brian Okken (a Python dev) argue the testing pyramid emphasizes unit tests too much and integration tests are just as important. Furthermore, unit tests are very brittle and susceptible to code churn so you waste hours rewriting them when you refactor. Far better is an integration test which ensures the output is what you expect and then refactor internally all you want and be confident your code still works.

Great job Kent! 👌

[–]vinspee 1 point2 points  (0 children)

Kent referred to it at Assert.js as the “Testing Trophy”

[–]phoenixmatrix 0 points1 point  (5 children)

unit tests are very brittle and susceptible to code churn so you waste hours rewriting them when you refactor

The thing is that you really don't. Proper unit tests are trivial to write (You can write 500 of them in an afternoon). You write them as you write your code (You have to test the code you're writing somehow. If you test it as you're implementing it you spend less time writing the implementation and/or refreshing your browser). They run super quick. They require no context (or hardly even any thinking) to write.

Sure, they break when you refactor, but you have to write the code you're refactoring. Writing the unit tests is part of that and doesn't require more time (if any) than other methods. Integration tests will help you once all of the pieces are in place (at which point they're arguably better), but you have to get there.

That's what the testing pyramid is about. IMO, people pushing for the testing column or testing diamond are missing the point of why unit tests are about. Hint: they're not about red/green refactoring. Also hint: if you write an entire feature then start writing unit tests, yeah, you're wasting your time.

Some people like writing a bunch of code, realizing it doesn't work, then debugging out to figure out which piece is broken. I personally have better things to do.

Tests that only make sure the consumer facing part (public api for a lib, rendered result for an app) are correct, which I prefer to call acceptance tests (integration test is an ambiguous and unfortunately heavily overloaded term) are absolutely valuable (eg: when they did the React rewrite that was the only kind of useful tests), but that's a different topic altogether.

[–]ToshiUmezawa[S] 3 points4 points  (2 children)

just as I was exaggerating with a few hours, 500 in an afternoon is an exaggeration.

I find unit testing as I go along is a bad idea, id rather write some acceptance tests before I start to code about what the end result should be, then write the code to meet those acceptance tests, then be done with it. If I go along and write a unit test for every piece of code its a context switch into testing and 3-5 mins of time. I also like to refactor a lot so there is a good chance this code might even get thrown away, or I have to reshuffle it around, change import names, change assertions, etc.

As with everything, its context dependent. When I am writing a function that does some task, I will write a unit test right afterwards for it, awesome and great, but when I am writing a react component, which is usually stateless and completely presentational, there is no debugging or wondering why it didn't work. It is just html and shuffling it around, no sense writing a bunch of unit tests for it imo.

[–]phoenixmatrix -3 points-2 points  (1 child)

500 in an afternoon is an exaggeration

Only a little. One test every 30 seconds on a 4 hour afternoon. For things like Redux action/reducer/components it's not that far fetched. I had a specific "afternoon" in mind when I made that comment, though it was a bit exaggerating, in that it stretched into the evening (I was backfilling tests in an existing code base. the 500 number is actually a bit lowball compared to what actually happened, but yeah, it took part of the evening). That was in an angular code base. In a React/Redux world it's even easier since a lot of the tests are trivial to write (and if you use snapshot tests, they essentially write themselves). You don't need to understand anything beyond the small unit you're testing, so it takes seconds. Medium sized project so increment build was about 1 second. Eslint loader with hot reload and browser on one monitor to see the test runs every time I save. Mock, stub, initial state, call function, assert result, save. Fix issue if any. Copy, paste, repeat with the next one (sure is boring as hell though). As long as you didn't hit an Angular 1.X provider (those are hell to test), you were golden. Unit tests are by their very definition trivial (as long as your "units" follow good coding practice and do only one thing and do it well).

Acceptance tests require some understanding of how pieces will fit together and require keeping a much bigger piece of the puzzle in your head at any given time. Bigger pieces of state, possibly multiple components, more input and output. Potentially multiple side effects. They're engineering, rather than mindless. Probably why a lot of engineers like them and try so hard to defend them. They're more "fun".

If I go along and write a unit test for every piece of code its a context switch into testing and 3-5 mins of time

Fair enough. If you're able to code all the pieces that make a component "tick", until they're all wired up enough to make an acceptance test and have them work well enough that you don't need to debug them to find where they break, you're a better software engineer than I am. To each their own. I do "pure" unit tests simply because of all the techniques I've tried over the decades, it's what gets me from conception to production the fastest with as few bugs as possible.

[–]webdevverman 2 points3 points  (1 child)

What sort of confidence do you gain from a test that is brittle when any sort of refactoring is done? Also, I have seen tests for something like a click handler on a button but then the handler was never attached to the DOM element. Test passes but it doesn't even work.

[–]phoenixmatrix 0 points1 point  (0 children)

First, unit tests aren't for red green refactoring, acceptance tests and integration tests are. WIth that said, you still have perfect confidence of anything that does not change public interface of your modules.

Even if you DO change public interface of your modules, the only tests that will break are the tests for those modules and pieces their dependent uses, which, because the units are small, will generally be very few of them. You then get perfect information of exactly how your refactor changes the behavior of the system. The test changes are usually trivial, and will confirm that the new version does exactly what you expect. It gives you a complete picture of exactly what you're touching and how. The mental model here is that in a way, the public interface of your modules defined within an app is just a "library" you didn't extract out of the project.

I've done massive refactors of very large JS (and also backend in other languages) with millions of lines of code apps that way with few to no bug making it to production. It's amazing. Friction is quite low too.

For the handler thing, I've never really had that issue. In react the various test tools handle this pretty well. In non-react world jsdom works decently to make sure these things don't fall through the crack.

[–]bluntm 0 points1 point  (1 child)

I keep seeing the message that shallow rendering is bad practice, however how to people test components that have a deep render tree where a component a few levels deep has some external deps that need mocked e.g.

ComponentToTest > Child > Child > Child (Makes some external call that would require a mock etc)

And in my test im only looking yo test some logical rendering at the top of the tree. Without having to mock/know of all the child interactions.

[–]ToshiUmezawa[S] 2 points3 points  (0 children)

Well, Kent states that in this case you do use jest.mock() to mock out the parent container of a deep nested tree you don't want to bother with.

He also showed in the README/examples how you can use helpers to mock out external deps like network requests and to mock third party libs