Great article. The only thing I would add is that anything other than `assertEquals(expectedValue, actualValue)` is not really permissible. Don't make assertions about 'portion' or 'subset' of your results, make assertions about the entire value! Otherwise, you're throwing away an opportunity to encode a constraint; sooner or later the undefined behavior will make itself known. We are writing tests to avoid that scenario in the first place, no?
Partial Disagreement on the phrasing of this - there are many classes of assertion like `assertThat(expected).containsInAnyOrder(foo,bar,baz);` that would not meet this. Order often does not matter and cannot be constrained. There are other things similar to this.
What if it's a repeated field of a protocol buffer? Parse it into a set and then check it? That's exactly what `assertThat(expected).containsInAnyOrder(foo,bar,baz);` is doing anyway.
I'm not very familiar with protobuf, but in searching, repeated field seems to have list- or set-like semantics, so yeah, I'd parse the value into a list or a set and compare it to a list or a set.
If you were going to work with protocol buffers frequently you'd probably quickly find yourself doing this over and over, and would then create the aforementioned `assertThat(expected).containsInAnyOrder(foo,bar,baz);`.
If find that if I am trying to test values that are not 'fully realizable' (infinite, lazy, etc) there's typically a temporal aspect involved, and I have a 'mock clock and timer' implementation so that I can call a `time.advance(t)` from within my tests and time advances in my system under test. This works because (as OP also noted) I only use my own mockable abstractions for answering the question 'what time is it?' and for instructing the system 'call me back at time t'. The net effect being I can fully define the behavior of the system within an arbitrary window of time—and collect the resulting values or behavior into some data structure which encodes what happened and when, and then compare it against expected results.
Re immutable: the values you're getting from your system under test and the expected values you're constructing in tests should always be immutable!
You might have separate assertions or separate tests for the different aspects. Insisting on only asserting equality forces you to lump them together, which can make the failure less clear.
Honest question, what kind of data type can't be compared to another instance of itself for equality? I have never encountered a situation where I have a value of a type which has some 'aspect' that cannot all be incorporated into the definition of `equals()` for its type. Seems to contravene the very notion of values and types. What am I missing?