168

How I can mock a field variable which is being initialized inline?

class Test {
    private Person person = new Person();
    ...
    public void testMethod() {
        person.someMethod();
        ...
    }
}

Here I want to mock person.someMethod() while testing the Test.testMethod() method for which I need to mock initialization of person variable. Any clue?

Edit: I'm not allowed to modify Person class.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Arun
  • 2,360
  • 2
  • 17
  • 23
  • 1
    This link might be helpful to you http://stackoverflow.com/questions/13645571/how-to-mock-a-private-dao-variable – Popeye Mar 23 '16 at 09:20
  • 3
    You should refactor your code so that you can pass in a mock for `Person`. Options include adding a constructor to do this, or adding a setter method. – Tim Biegeleisen Mar 23 '16 at 09:23

9 Answers9

148

Mockito comes with a helper class to save you some reflection boiler plate code:

import org.mockito.internal.util.reflection.Whitebox;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    Whitebox.setInternalState(underTest, "person", mockedPerson);
    // ...
}

Update: Unfortunately the mockito team decided to remove the class in Mockito 2. So you are back to writing your own reflection boilerplate code, use another library (e.g. Apache Commons Lang), or simply pilfer the Whitebox class (it is MIT licensed).

Update 2: JUnit 5 comes with its own ReflectionSupport and AnnotationSupport classes that might be useful and save you from pulling in yet another library.

Udo Held
  • 12,314
  • 11
  • 67
  • 93
Ralf
  • 6,735
  • 3
  • 16
  • 32
  • I'm spying my target object because of other reasons and in this case when my object is spy, I cannot set internal state this way. – Arun Mar 23 '16 at 13:32
  • Why not? I am using it with spies. Create a Person instance. Stub whatever needs stubbing, then set it on your Test instance. – Ralf Mar 23 '16 at 14:02
  • Warning: Whitebox is in the `internal` package and does not seem to work anymore on Mockito 2.6.2. – Nova Jan 15 '17 at 16:51
  • You can use FieldSetter.setField(). I've given an example below for the same. – Raj Kumar Aug 14 '18 at 06:33
  • This seems brittle because there is no compiler check that you are using the correct field name, and it could break when the code changes without notification. Using the `@PowerMockRunner` class annotation as described in another answer seems like the way to go. – Joe Coder Nov 05 '19 at 03:27
  • @JoeCoder, depending on the code change the PowerMock solution is very likely to break just the same without the compiler catching it. E.g. if you add another `Person` field to the class under test or remove the person member. I don't see the harm though. As long as the test verifies expected behavior properly it will go red after the code change and thus fulfills its purpose. – Ralf Nov 05 '19 at 08:23
  • @Ralf PowerMock's injection of `Person::new` occurs for all instances of `Person`, so it would still work. The one drawback I've found is that some test runners (such as SonarQube's code coverage tool) do not work with `@PowerMockRunner`. – Joe Coder Nov 05 '19 at 08:36
  • @JoeCoder, the behavior to be verified for the second `Person` is likely to be different from the first person... – Ralf Nov 05 '19 at 08:38
  • @Ralf Not a problem. `Person::new` is only injected in the class referenced in the `@PrepareForTest` annotation, e.g `@PrepareForTest({PersonFactory.class})`. You could create regular mock `Person` instances and pass them into the injector as needed in the test code, e.g. `PowerMockito.whenNew(Person.class).withArguments("Frank").thenReturn(frank);` – Joe Coder Nov 05 '19 at 08:58
  • @JoeCoder, of course you can do that. Just like you can adapt the plain vanilla Mockito test. But in both cases the compiler won't tell you that you have to do it. Which was your criticism of the solution using just Mockito and some reflection support. – Ralf Nov 06 '19 at 06:15
  • @Ralk My criticism is this: if the private instance member `Test::person` is changed to `Test::human`, this is an implementation detail and the unit test should not fail on that, but it does when reflection is used, and it fails silently. – Joe Coder Nov 06 '19 at 07:08
  • @JoeCoder, I don't see why it should fail silently? The test will go red, most likely because the reflection code will through an exception or at the latest because the code under test throws an NPE. – Ralf Nov 06 '19 at 07:35
  • 1
    @Ralf Because changing a reference name in Java should always result in a compilation error. – Joe Coder Nov 08 '19 at 13:20
112

In case you use Spring Test try org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
97

Pretty late to the party, but I was struck here and got help from a friend. The thing was not to use PowerMock. This works with the latest version of Mockito.

Mockito comes with this org.mockito.internal.util.reflection.FieldSetter.

What it basically does is helps you modify private fields using reflection.

This is how you use it:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);
    // ...
    verify(mockedPerson).someMethod();
}

This way you can pass a mock object and then verify it later.

Here is the reference.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Raj Kumar
  • 1,547
  • 14
  • 19
  • 11
    `FieldSetter` is not available anymore in Mockito 2.x. – Ralf Aug 14 '18 at 07:45
  • 5
    @Ralf I'm using version mockito-core-2.15.0. It's available there. https://static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/mockito/internal/util/reflection/FieldSetter.html. Still beta though. – Raj Kumar Aug 21 '18 at 04:37
  • 1
    @RajKumar the `Class#getDeclaredField` accepts single param, so the parens need to look like this`FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);`. That was how I got it wrong and thought might be a good idea to fix it in your example. Thanks for replying. – Dapeng Li Jun 26 '19 at 07:54
  • 3
    using internal API is not the best idea – David stands with Ukraine Dec 03 '19 at 08:23
  • @RajKumar Nice, but as Zimbo Rodger mentioned: Is it a good idea to use an internal API? Why is it internal acutally? It is so useful for testing. – Willi Mentzel Jul 08 '20 at 14:12
  • 2
    I don't understand, how could this work with private field? underTest.getClass().getDeclaredField("person") will throw NoSuchFieldException I think. – lidlesseye May 23 '22 at 02:40
36

I already found the solution to this problem which I forgot to post here.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Test.class })
public class SampleTest {

@Mock
Person person;

@Test
public void testPrintName() throws Exception {
    PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);
    Test test= new Test();
    test.testMethod();
    }
}

Key points to this solution are:

  1. Running my test cases with PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Instruct Powermock to prepare Test.class for manipulation of private fields: @PrepareForTest({ Test.class })

  3. And finally mock the constructor for Person class:

    PowerMockito.mockStatic(Person.class); PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);

Arun
  • 2,360
  • 2
  • 17
  • 23
  • 12
    Your explanation talks about the `mockStatic` function, but that is not represented in your code example. Should the code example have the `mockStatic` call, or is that not required for constructors? – Shadoninja Oct 16 '18 at 21:39
12

Following code can be used to initialize mapper in REST client mock. The mapper field is private and needs to be set during unit test setup.

import org.mockito.internal.util.reflection.FieldSetter;

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());
Jarda Pavlíček
  • 1,636
  • 17
  • 16
10

if u are using spring boot test and cant find neither of WhiteBox, FeildSetter; u can simply use org.springframework.test.util.ReflectionTestUtils

this is an example:

import org.springframework.test.util.ReflectionTestUtils;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    ReflectionTestUtils.setField(underTestObject, "person", mockedPerson);
    // ...
}
Seyed Ali Roshan
  • 1,476
  • 1
  • 18
  • 37
6

Using @Jarda's guide you can define this if you need to set the variable the same value for all tests:

@Before
public void setClientMapper() throws NoSuchFieldException, SecurityException{
    FieldSetter.setField(client, client.getClass().getDeclaredField("mapper"), new Mapper());
}

But beware that setting private values to be different should be handled with care. If they are private are for some reason.

Example, I use it, for example, to change the wait time of a sleep in the unit tests. In real examples I want to sleep for 10 seconds but in unit-test I'm satisfied if it's immediate. In integration tests you should test the real value.

Hugo Dias
  • 341
  • 3
  • 13
6

The best way until now, I think that is

org.springframework.test.util.ReflectionTestUtils

Im about to mock the private String mockField in FooService.class inside FooServiceTest.java

FooService.java:

  @Value("${url.image.latest}")
  private String latestImageUrl;

FooServiceTest.java:

  @InjectMocks
  FooService service;

  @BeforeEach
  void setUp() {
    ReflectionTestUtils.setField(service, // inject into this object
        "latestImageUrl", // assign to this field
        "your value here"); // object to be injected
  }
Le Vu
  • 61
  • 1
  • 3
3

commons-lang3

import org.apache.commons.lang3.reflect.FieldUtils;

FieldUtils.writeField(object, fieldName, value, true);
Mike
  • 20,010
  • 25
  • 97
  • 140