Tales from the jar side: A Mockito custom matcher, Teaching to empty rooms, and Funny tweets
Dad joke: If you or a loved one has been forced to wear a mask and glasses at the same time, you may be entitled to condensation (rimshot)
Welcome fellow jarheads to Tales from the jar side, the Kousen IT newsletter, for the week of February 14 - February 20, 2022. This week I taught Week 2 of my Spring and Spring Boot in 3 Weeks course on the O’Reilly Learning Platform, and both my Java Testing Part I: JUnit 5 and AssertJ and my Java Testing Part II: Mocks, Stubs, and Spies with Mockito courses as NFJS Virtual Workshops.
Regular readers of this newsletter are affectionately known as jarheads, and are widely acknowledged to be far more intelligent, sophisticated, and attractive than the average newsletter reader. If you wish to become a jarhead, please subscribe using this button (with a caption this time — ooh, aah):
Whenever my newsletter becomes too long for email, you can read the full version online. If you miss an issue, the entire archive is there as well, and everything is free and always will be.
Argument Matchers with Primitives
I like to do live coding during my presentations. The audience tends to like it, because it helps them see the process you go through when writing the code. It also shows how comfortable you are with the editor, and I like to take that opportunity to share some of the keyboard shortcuts I’ve learned over the years. So I enjoy live coding, and I do it well, especially if I’m giving a talk I’ve given before.
This week I finally got the chance to teach my Mockito class with my new materials, which are based on a book I’m in the middle of writing. For those who don’t know, Mockito is a Java framework that generates “mock” objects for you, allows you to determine how they respond to methods, and then verifies that the interactions went the way you expected.
I would normally go into more detail about what that all means, but not this time. I will in future newsletters and blog posts. What’s relevant this week is that I tried to live-code a feature during class and it didn’t work. Oops.
In my course, I have a small example that tests a class called NumberCollection. The purpose of that class is to wrap a List<Integer> as a dependency, because the Mockito documentation is very fond of showing you how to mock a List, even though they even say not to do that in reality.
Seriously, they say that, and then do it anyway. Here’s the actual section of the docs:
Like most relationships, it’s complicated.
Here’s the basic structure of NumberCollection:
NumberCollection depends on a List, which is injected into the collection using the constructor. The whole point is that I don’t want to use a real list, but rather have Mockito generate a fake one. In any case, the method getTotalUsingLoop calls two methods on the List:
size(), which should return how many elements are in the list, and
get(int), which retrieves the individual elements from their index values.
If I have Mockito generate a List, I need to make sure that both those methods return what I want. That’s called setting expectations on the mock.
Here’s the relevant test:
Lines 6 asks Mockito to create a mock list. Lines 9 - 12 tell the mock what to do when the test class calls size() or get(int). Line 15 creates a NumberCollection with the mock list as an argument. Line 18 checks that the getTotalUsingLoop method adds up the numbers in the list properly.
The interesting part is the last two lines, 21 - 22. The first verifies that the NumberCollection called the size() method on the mock exactly once. The last line checks that the NumberCollection called the get() method on the list three times, with any integer argument.
The anyInt() method is called an argument matcher. It matches any integer, so I only need one verify statement instead of three. Mockito includes a class called ArgumentMatchers (plural) that has lots of static methods like that, including anyLong, anyDouble, anyBoolean, anyChar, anyString, and so on.
You can also use the method argThat(ArgumentMatcher) to provide your own argument matcher. ArgumentMatcher (singular) is an interface that you can implement to do what you want. The good part is that the interface has only a single abstract method:
boolean matches(T argument)
That makes ArgumentMatcher a functional interface, which means you can implement it with a simple lambda expression. That’s what I wanted to demonstrate during class.
So I went to line 22 above, and I replaced the call to anyInt() with a lambda:
verify(mockList, times(3)).get(
argThat(n -> n < 3)
);
where I’m using a trivial lambda expression that takes an integer n and verifies that all three calls to the get() method used an integer less than 3. When I set the expectations on that method, I only set it for arguments 0, 1, and 2, and the method I’m testing returned the right sum (6), so I thought everything was going to be fine.
The result, however, was:
Cannot invoke "Integer.intValue()" because the return value of "argThat(org.mockito.ArgumentMatcher)" is null
My response was: uh? Why in the world would argThat return null? I’ve used argThat before, though not here, and never had a problem. What’s going on?
I wasn’t able to resolve this on the fly during class, but I did find the issue (sort of) afterwards. The JavaDocs for the argThat method include this little gem:
NullPointerException auto-unboxing caveat. In rare cases when matching primitive parameter types you *must* use relevant intThat(), floatThat(), etc. method. This way you will avoid NullPointerException during auto-unboxing. Due to how java works we don't really have a clean way of detecting this scenario and protecting the user from this problem. [emphasis added] Hopefully, the javadoc describes the problem and solution well. If you have an idea how to fix the problem, let us know via the mailing list or the issue tracker.
Apparently I hit one of those “rare cases” where auto-unboxing an Integer into a primitive int resulted in a NullPointerException.
At least I knew what to do to fix it. I simply replaced the call to argThat with a call to intThat:
verify(mockList, times(3)).get(
intThat
(n -> n < 3));
and everything works.
The JavaDocs on intThat have a slightly different version of the same warning:
Allows creating custom int argument matchers. Note that argThat(org.mockito.ArgumentMatcher<T>) will not work with primitive int matchers due to NullPointerException auto-unboxing caveat.
There you go. Even though I fumbled a bit during class, I fixed the problem and checked the results into the GitHub repository for the course. Equally important, I learned something about Mockito that I didn’t know before, in time to include it in the book. Now I can share this newly discovered wisdom with the maybe three other developers world-wide who might encounter the same problem.
Teaching to Nobody
A lot of presenters find online conferences frustrating because it limits the interactions with the audience. I get that, though I seem to mind it less than others. I’m pretty good at keeping an eye on the chat box or whatever other text feedback mechanism is available. Also, my presentations tend to be more demos than workshops, meaning I do more working with existing code than asking the attendees to do anything.
I used to think that was a bug rather than a feature, but now I’m not so sure. It’s true that most people learn by doing, so they prefer to have exercises to work through, but it’s also true that my online courses are severely time limited. Like everything in education, they’re sold to people who only care about how much material you’re going to cover for a certain cost, and the people buying the courses are not usually the ones in the courses themselves. More, as a student if your experience is dominated by the highly dysfunctional educational system in this country, you’re accustomed to watching demos, asking questions, and only doing the exercises later. Also, the attendees tend have very different skills and experience levels, so some of them zip through the exercises with no problems, while others require a lot more support.
My compromise in training classes has been often to prepare exercises that are very explicit — type this, run that, delete this line and replace it with that one — which has the advantage of being predictable. Earlier in my career as an instructor, I used to worry that labs like that gave the students a false sense of progress, because they feel like you’re doing something when you’re really not. I preferred exercises that gave vague instructions and then let the student flail for a while.
Over time I’ve learned not to do that. Everybody likes a “Hello, World” example (as they’re called in my field), because it gives them the flavor of what’s going on along with at least a small feeling of accomplishment. The more detailed labs are also easier on the instructor, because I know how long they take, and I can even run through them in front of the students and then ask them to try the exercises later.
For several of my courses, I don’t even have those written instructions. Instead I have a GitHub repository with lots of code examples, and I basically walk through them and run them as we go along. That’s not ideal, but it does cover a lot of ground quickly. The students can always go back to the code base and try things out. During class, as they ask questions, I’ll add or revised some of the code, and check it in when we’re done. It’s better when I have prepared exercises, but creating them is a lot of work that has to be maintained later. I should do that, but I haven’t on many of my courses.
This week I faced an unusual situation that comes up once in a long while. The virtual workshops on the No Fluff Just Stuff tour are often sold on a subscription basis, which means the clients can register for many of them and only plan to watch the recordings later. That means I can wind up with dozens of students, if they suddenly decided to attend, or almost nobody in the classroom.
This week my NFJS virtual workshops were:
Java Testing, Part 1: JUnit 5 and AssertJ
Java Testing, Part 2: Mocks, Stubs, and Spies with Mockito 4
For whatever reason, the number of subscribers to both was very low. As it turned out, only one student attended both workshops. Also, that student had only moved into development fairly recently, so he just wanted to see how things worked without answering a lot of questions. To top it all off, he had an hour-long meeting during the second workshop, right in the middle, so I had nobody for about an hour of the 3 1/2 hour workshop.
In other words, I spent most of the time during both workshops talking to a wall.
That’s not as bad as it sounds. Again, the NFJS workshops are recorded, and many students may have been planning to watch the recordings all along. Fortunately, while these two workshops in particular can be very interactive, they don’t have to be. They can be glorified presentations if necessary, and this time it was necessary.
I’ve made plenty of videos without an audience during the recording sessions, so I know how to do it and how to talk to a camera that represents the students watching later. I admit it’s a tougher way to go. I enjoy interacting with an audience, answering their questions, customizing the materials to what they need, and trying out wild experiments in front of everybody just to see what happens. I lose that interaction if no one is there, or if I’m on a Zoom call and nobody is willing to turn on their camera or ask questions.
But to be totally mercenary for a moment, I still get paid either way, and that helps. :)
The workshops were fine, though not ideal. I was able to interact with the one guy who was (mostly) there, and he seemed happy with the situation. During the hour he was stuck in a meeting in the second workshop, I made the argThat/intThat mistake discussed in the first section of this newsletter. If I hadn’t told you about it, the only people who would have ever known about it would be the few students who watch that recording, and that’s not a big number. After all, next time I teach the course, there will be a new recording, and I’ll show them the feature I learned as an interesting trap that of course I knew all along, rather than falling directly into it.
So the workshops this week were not my favorites, but they weren’t a disaster, either. I only hope I don’t have to do that again soon.
Tweets 'N Stuff
Correlation does not imply causation
I can’t wait to use this:
It reminds me of my favorite correlation vs causation comic, from XKCD, of course;
Inspired Physics
I love this idea:
There are so many good and bad implications of that idea.
Another clever idea
II wonder what movie they are going to see? Presumably Shaun the Sheep.
Yes, I’ve noticed this too
This is why I’m glad I get a few comments on the newsletter, but not a lot. I don’t want to adopt an overly defensive writing style, but that will probably be inevitable when I eventually go viral. :)
With great power…
… comes a thankless job with too much time and effort, and for what?
Resolving the age-old dilemma
This tweet:
Led to the Cube Rule web site, which explains everything:
I’ve always believed a Pop-Tart is a sandwich, but I’m not sure Lucky Charms count as nachos. YMMV.
Weird Name, But Good Advice
I stopped tweeting my Wordle results a couple weeks ago, but I’m still playing. I just don’t share it any more.
(In case you missed the joke, STFU stands for “be quiet,” or something like that.)
No value judgement, btw. Feel free to keep sharing your results if you like.
The Truth Revealed
The boss said the quiet part out loud.
As a reminder, you can see all my upcoming training courses on the O’Reilly Learning Platform here and all the upcoming NFJS Virtual Workshops here.
Last week:
Spring and Spring Boot in 3 Weeks, week 2, on the O’Reilly Learning Platform
Java Testing, Part I: JUnit 5 and AssertJ, an NFJS Virtual Workshop
Java Testing, Part II: Mocks, Stubs, and Spies with Mockito 4, an NFJS Virtual Workshop
Next week:
Spring and Spring Boot in 3 Weeks, week 3, on the O’Reilly Learning Platform