ChatGPT解决这个技术问题 Extra ChatGPT

Mock private static final field using mockito or Jmockit

I am using private static final LOGGER field in my class and I want LOGGER.isInfoEnabled() method to return false. How can I mock the static final field by using mockito or jMockit

My class is:

  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  public class Class1 {

  private static final Logger LOGGER = LoggerFactory.getLogger(Class1.class);

    public boolean demoMethod() {
       System.out.println("Demo started");
       if (LOGGER.isInfoEnabled()) {
         System.out.println("@@@@@@@@@@@@@@ ------- info is enabled");
       } else {
         System.out.println("info is disabled");
       }
       return LOGGER.isInfoEnabled();
    }
  }

and its junit is :

import mockit.Mocked;
import mockit.NonStrictExpectations;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.testng.Assert.*;
import com.source.Class1;

public class MyTest {

  @InjectMocks
  Class1 cls1;

  @BeforeMethod
  public void initMocks() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void test(@Mocked final Logger LOGGER) {

    new NonStrictExpectations() {
      {
        LOGGER.isInfoEnabled();
        result = false;
      }
    };
    assertFalse(cls1.demoMethod());
  }
}

when I run it the result is:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.test.MyTest
Configuring TestNG with: TestNG652Configurator
Demo started
@@@@@@@@@@@@@@ ------- info is enabled
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.9 sec <<< FAILURE! - in com.test.MyTest
test(com.test.MyTest)  Time elapsed: 0.168 sec  <<< FAILURE!
java.lang.AssertionError: expected [false] but found [true]
        at com.test.MyTest.test(MyTest.java:35)


Results :

Failed tests:
  MyTest.test:35 expected [false] but found [true]

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.899s
[INFO] Finished at: Mon Jun 08 12:35:36 IST 2015
[INFO] Final Memory: 16M/166M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test (default-test) on project JMockDemo: The
re are test failures.
[ERROR]
[ERROR] Please refer to D:\perfoce_code\workspace_kepler\JMockDemo\target\surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

I am new to jmockit and I want my above junit case to run successfully. And I have to use JMockit or mockito, can't use Powermockito. Please help.

You can create a setter method for these elements and set a mock logger while testing.
Yes it is working that way,but I have to make logger not final and also add a setter method. Is't there any way of doing it without changing the actual class.
Just change @Mocked Logger to @Capturing Logger, it should work.
Thanks @Rogério that is also working, now I got another working solution :-)
see stackoverflow.com/a/60988775/812093 for how to do it with plain Mockito

N
Nitin Dandriyal

One way is using reflection get rid of final modifier from the field and then replace the LOGGER field with Mocked one

public class Class1Test {
    @Test
    public void test() throws Exception {
        Logger logger = Mockito.mock(Logger.class);
        Mockito.when(logger.isInfoEnabled()).thenReturn(false);
        setFinalStatic(Class1.class.getDeclaredField("LOGGER"), logger);
        Class1 cls1 = new Class1();
        assertFalse(cls1.demoMethod());
    }

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);        
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
}

Wow great, this is the solution I was looking for, thank you very much :-)
Why did you make setFinalStatic method static, is there any specific reason?
This is very dangerous since it alters the static instance for your whole ClassLoader. Any subsequent test/class will have that mocked instance of the logger. This should also not be used when running tests in parallel. This can lead to serious multi-threading problems in your tests, be cautious about that.
stupid mocking frameworks, if I want to mock it, then it should automatically change the modifier for me.
@IlkerCat I quickly searched and haven't found an example in one of my project (it's been quite a long time). If I remember correctly that should work: stackoverflow.com/questions/5385161/… :D
Z
ZakiMak

The accepted solution shouldn't work with JDK 12. The reason can be seen here.

It is easy to do it using PowerMockito (tested with version 2.0.9). You can use the Whitebox.setInternalState method to do it for you.

Example:

Whitebox.setInternalState(MyTestClass.class, "myCar", carMock);

MyTestClass is the class containing the field.

myCar is the variable name of the field.

carMock is some mock you want to pass.


This worked for me, and it is nice and simple. I didn't have to remove the final modifer from the logger
PowerMockito Whitebox 2.0.9 throws exception on private static final: Caused by: java.lang.IllegalAccessException: Can not set static final bla-bla field bla-bla. So reflection is only solution imho
This does not work if field is only final, not static as well.
K
Kalyan Chavali

I think mockito or jMockit cant mock static final classes as they try to override the methods while unit testing. However, powerMockito can as it uses Reflection and mocks the static final class/methods.


You can't override final methods.. You can only do that if you use reflection.
R
RaT

Changing "@Mocked Logger" to "@Capturing Logger" in the parameter makes it work. like

  @Test
  public void test(@Capturing final Logger LOGGER) {

    new NonStrictExpectations() {
      {
        LOGGER.isInfoEnabled();
        result = false;
      }
    };
    assertFalse(cls1.demoMethod());
  }