How Intellij Tricked Me To Think Mockito Is Broken

Posted by

My Little DOH! Moment

Did you ever debug a piece of code, and said to yourself: “this shouldn’t happen!”?
Well, me too, so when it happened to me and I learned why it happened, I thought it’s worth sharing.


In my project I have two classes, let’s for simplicity call them Bouncer & EntranceChecker, Now I know that Bouncer is basically EntranceChecker, but bare with me, it’s not really important to the story.

Bouncer holds EntranceChecker and every time a Person tried to enter,
Bouncer calls EntranceChecker’s isInTheGuestList method, and if the person in the list Bouncer return not to block, else it blocks.

public class Bouncer {
    private final EntranceChecker entranceChecker;

    public Bouncer(EntranceChecker entranceChecker) {
        this.entranceChecker = entranceChecker;
    }

    public boolean shouldBlock(Person person) {
        return !entranceChecker.isInTheGuestList(person);
    }
}
public class EntranceChecker {
    private final Set<String> names;
    public EntranceChecker(Set<String> names) {
        this.names = names;
    }

    boolean isInTheGuestList(Person person) {
        if(person == null) {
            return true;
        }
       
        return names.contains(person.getName());
    }
}

Like any good programmer I wrote Bouncer a unit test to check it works.
I used Mockito to Mock EntranceChecker.
When it receives a Person that is in the list (“mockInListPerson”) it should return true, and otherwise false (“mockNotInListPerson”).

Looks simple enough, right? should pass both tests, right? Want to guess what happened when I tried to run it?

@RunWith(MockitoJUnitRunner.class)
public class BouncerTest {
    @Mock
    private EntranceChecker mockEntranceChecker;
    
    @Mock
    private Person mockInListPerson;
    
    @Mock
    private Person mockNotInListPerson;
    
    private Bouncer bouncer;
    
    @Before
    public init() {
        Mockito.when(mockEntranceChecker.isInTheGuestList(mockInListPerson)).thenReturn(true);
        Mockito.when(mockEntranceChecker.isInTheGuestList(mockNotInListPerson)).thenReturn(false);                     
        this.bouncer = new Bouncer(mockEntranceChecker);
    }

    @Test
    public boolean shouldBlock_nullPerson_notBlock() {
      boolean result = bouncer.shouldBlock(mockInListPerson);
      Assert.assertTrue(result);
    }
    
    @Test
    public boolean shouldBlock_nullPerson_block() {
      boolean result = bouncer.shouldBlock(mockNotInListPerson);
      Assert.assertTrue(result);
    }
}

Got this:

When I tried to debug it, I saw that it happens when the init() code runs
and it tried to execute the line below:

 

It enters(!) the EntranceChecker’s isInTheGuestList method, and throws NPE on the line below.

 


It turned out The NPE was thrown because names wasn’t initialized.
Which is true, I didn’t initialize it, it’s shouldn’t have been running in the first place! it’s mocked! that’s the all idea of mock!
Haa I have a bug in Mockito!!! it’s broken! I broke Mockito!

After way more hours than I am proud to say, I calmed down and started to think rationally again, and figured it out.
EntranceChecker, was simply missing a small little public in front of isInTheGuestList.

public class EntranceChecker {
    private final Set<String> names;
    public EntranceChecker(Set<String> names) {
        this.names = names;
    }

    public boolean isInTheGuestList(Person person) {
        if(person == null) {
            return true;
        }
       
        return names.contains(person.getName());
    }
}

The moment I did it, Mockito came back to life and managed to mock my method.

 

Why Was This The Problem?

I don’t want to go into too many details on how mockito works, especially when Reinhard Seiler explains this so good in his blog: explanation how proxy based mock work, I strongly recommend you read it if you are writing tests and want to know how the tools you use work.

I’ll just say that when you ask Mockito to mock a class for you, it basically wraps it in a wrapper class and answers what you ask of him.

The problem is, that it cannot access private method and package private methods fall under that same restriction.
Once I turned the method from package private methods to public, Mockito could “see” it again and mock it. when it failed to see it, the line of code simply tried to call the real method and crashed.

When I went back to Intellij and looked at the metod, I remembered why I did it in the first place. Intellij suggested it to me.

Doing that, didn’t break any thing and so I assumed I was OK.
But Mockito is not part of that package, and so when I asked Mockito to mock the class it was blind to that method and failed.

It’s a weird behavior on the part of Intellij not to be able to detect this.
When I tried to pass Mockito a private method, it knew to prevent me from doing it:

isTheGuestList(Person) has private access in EntranceChecker

Bottom Line:

I find Intellij suggest most helpful and try to accept them whenever I can, but this time it was the thing that gave me an headache, for more hours than I feel comfortable admitting.

I can offer excuses why it took me so long to find this simple thing:

  • I just upgraded the Intellij version and I was worried it might have broken the mockito annotation or something. so I reverted it back, just to find out it wasn’t the issue.
  • I tried to rebuild the all project — more then once — and run other tests with similar code.
  • It was very late, and I was tired.

Bottom line, after I found it, and felt really stupid that I didn’t think about it in the first place.
Because I am a strong believer in the practice of sharing one stupidity with others, I decided to make it as public as possible.
In the hope that, you found my stupidity funny enough that you share it with your friend and colleagues.
And maybe one day you or one of your colleagues will face similar issue, have a vague memory that you already seen this problem, fix it in seconds and go on in your merry way.

Thanks for reading

Create Your Content Campaign Today!