0

I would like to mock the following class:

public class MyRunner {
    private String name;
    private User user;//not final
    public MyRunner(String name, User user) {
      this.name = name;
      this.user = user
    }
    //...something complicated in the methods
}

Just like with JMockit

@ExtendWith(MockitoExtension.class)
public class MyRunnerTest {
    @Inject
    private String name = "sth";
    @Inject
    private User user;
    @Tested
    private MyRunner tested;
}

Similarly with Mockito 3,

@ExtendWith(MockitoExtension.class)
public class MyRunnerTest {
    @Mock
    private String name;
    @Mock
    private User user;
    @InjectMocks
    private MyRunner tested;
    @Test //from JUnit5
    public void testSth() {
      //...
    }
}

Problem : The injection of the name string into the MyRunner during construction would fail due to the fact that String is a final class. Got error message like:

Cannot mock/spy class java.lang.String
Mockito cannot mock/spy because :
 - final class

It is possible to intialize the tested class with new keyword:

@ExtendWith(MockitoExtension.class)
public class MyRunnerTest {
    private String name = "sth";
    @Mock
    private User user;
    private MyRunner tested = new MyRunner(name, user);
    @Test //from JUnit5
    public void testSth() {
      //...
    }
}

However, the solution above is not as fluent as merely using the annotation

Question : How to inject the final class into the object annotated with @InjectMocks instead of constructing it with new keyword explicitly?

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
Rui
  • 3,454
  • 6
  • 37
  • 70

1 Answers1

0

There are many approaches to this. Here are 3 conceptually different ones:

Option 1

First off, if you're really talking about the String, than probably mocking it doesn't have much sense: Basically your example doesn't require mockito at all. You can simply:

public class UserTest {
  private final String NAME = "sampleName;

  private User tested = new User(NAME);

 ...
}

Option 2

Now, assuming that you're not really talking about String class here, the best option is to program by Interfaces for which creating Mocks is fairly simple:


public class User {
    public User(Name name) {
      ...
    }

}

public interface Name {
   String getValue();
}

@ExtendsWith(MockitoExtension.class)
public class UserTest

   @Mock
   private Name name;

   @InjectMocks
   private User tested;

Option 3

If you're absolutely required to mock static class, you can do that in mockito but it requires some additional setup: basically you create a file org.mockito.plugins.MockMaker in the directory src/test/resources/mockito-extensions that will contain:

mock-maker-inline

See this tutorial for more information

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • The name is simply a string, isn't wrapping it way redundant? With JMockit, it is possible to inject final class into a `@Tested` instance merely by means of `@Inject` annotation, so this question in the domain of *Mockito* came into my mind – Rui Apr 26 '21 at 21:09
  • I totally agree that if its really a string, then its totally redundant to wrap it, thats why I'stated that the second dircection (the one with the interfaces) aplies only if you're not talking about strings here. So if its a string - I would personally have considered the first solution (the one without mocking at all - as mocking a string sounds to me an overkill). The third solution is a "general" way to mock final classes (similarly to what you've stated in the comment about JMockit). – Mark Bramnik Apr 27 '21 at 02:47
  • Thanks again for the improved answer. Option 1 and option 2 both have redundancy flaw. I then looked into option 3, but not able to figure out how to use that in the code. Moreover, the `mock-maker-inline` has compatibility issue with PowerMockRunner: https://github.com/powermock/powermock/issues/1034 – Rui Apr 27 '21 at 12:02
  • As for Powermock - I can't really comment - I personally try to avoid using it, maybe for legacy code or something... As for the option 1 - why do you think its redundant? Basically it boils down to why do you want to mock java.lang.String instead of using some "artificial" on the regular string? – Mark Bramnik Apr 27 '21 at 12:13
  • Yes :) I modified the question more in detail and added option 1 there, then told why I would prefer to using @InjectMocks instead of `new` keyword. I have been using option 1 currently :) But still feel it would more fluent to use merely `@InjectMocks` – Rui Apr 27 '21 at 12:16
  • Aha, now I'm getting - so you have a mix of mocks and real objects. In this case y use `@Spy` on the real object that you want to inject (`@Spy String name` in your case). `@InjectMock` should work with `@Spy` annotated fields. See https://stackoverflow.com/questions/20270391/mockito-inject-real-objects-into-private-autowired-fields and https://github.com/mockito/mockito/issues/174 – Mark Bramnik Apr 27 '21 at 12:27
  • Thanks for that link. I tried just now with `@Spy` but it failed for the same reason: `Cannot mock/spy class java.lang.String` In the answer in that link, the `SomeService` annotated with `@Spy` is not `final`, either – Rui Apr 27 '21 at 12:46
  • Ah, yes, you're right. In this case probably it cannot be done in Mockito, since InjectMock works only with fields annotated with `@Spy` or `@Mock`. Probably worth submitting a request for enhancement, they state in the documentation of InjectMock that its "dependency injection" capabilities are rather limited... – Mark Bramnik Apr 27 '21 at 13:31
  • Thanks a lot for the last comment, that makes great sense to me :) I tried to find that part of document, but didnt find yet – Rui Apr 27 '21 at 15:24