0

I have a singleton Class as follows:

 public class PropertiesSingleton {
private static PropertiesSingleton instance;

private static ApplicationContext ctx = new 
AnnotationConfigApplicationContext(PropertiesConfig.class);

public static PropertiesSingleton getInstance(){
    if(instance == null){
        synchronized (PropertiesSingleton.class) {
            if(instance == null){
                instance = new PropertiesSingleton();
            }
        }
    }
    return instance;
}

public Environment env(){
    return ctx.getEnvironment();
}
}

The PropertiesConfig Class as follows:

@Configuration
@ComponentScan("someComponent")
@PropertySources({@PropertySource("file:${someName}/foo/foo.properties"),
@PropertySource("classPath:Properties")
})
public class PropertiesConfig {
@Autowired
private Environment env;
}

And it is being called in Controller as Follows:

@CrossOrigin
@RestController
@RequestMapping(value="/mappingUrl")
public class MappingController{ 
static Environment env = PropertiesSingleton.getInstance().env();
}

What I am trying to do is get the mocked value for env in controller and I am trying it following way:

@Before
public void setUp() throws Exception {
     MockEnvironment env = new MockEnvironment();
     env.setProperty("serverName", "test");

     PropertiesSingleton environmentMock = 
     PowerMockito.mock(PropertiesSingleton.class);
     Whitebox.setInternalState( PropertiesSingleton.class, "INSTANCE", 
     environmentMock );
     when(environmentMock.getInstance().env())
        .thenReturn(env);        
     }

But I get following errors:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

and looking at stack trace I see following:

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [propertiesConfig.class]

org.eclipse.debug.core.DebugException: com.sun.jdi.ClassNotLoadedException: Type has not been loaded occurred while retrieving component type of array.

Any help is appreciated......

EDIT:

Here is my full test method--

import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.powermock.modules.junit4.PowerMockRunner;
 import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.junit.Before;
import org.apache.log4j.Logger;
import static org.mockito.Mockito.*;
import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.nio.charset.Charset;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
@RunWith(PowerMockRunner.class)
@PrepareForTest(PropertiesSingleton.class)
@TestPropertySource(locations="classpath:someclassPath")
public class MappingControllerTest{

private MockMvc mockMvc;

JSONObject  obj = new JSONObject("{" 
        + "\"_id\": \"184DZ01C\"," 
        + "\"text\": \"textTest\","                  
        + "\"image\" : \"Test.png\"," 
        + "\"link\" : \"www.testFile.com\"," 
        + "\"title\" : \"testTitle\"," 
        + "\"weight\" : \"0\","
        + "}");

public static final MediaType APPLICATION_JSON_UTF8 = new 
MediaType(MediaType.APPLICATION_JSON.getType(), 
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));


@InjectMocks
@Spy
 MappingController _mappingController = new MappingController();



@Mock
private Appender logAppender;  



@Mock
private Environment environmentMock;


@Mock
private PropertiesSingleton singletonMock;




 @Before
    public void setUp() throws Exception {
     Logger logger = Logger.getLogger(ReviewWfEndpoint.class);
     logger.addAppender(logAppender);
     logger.setLevel(Level.INFO);

    }




 @Test
 public void TestReviewWfEndpoint_pass()
  throws Exception {    
    String expectedServer = "test";
    String expectedServerPropertyName = "serverName";

    PowerMockito.mockStatic(PropertiesSingleton.class);
    when(PropertiesSingleton.getInstance()).thenReturn(singletonMock);
    when(singletonMock.env()).thenReturn(environmentMock);
    when(environmentMock.getProperty(expectedServerPropertyName))
    .thenReturn(expectedServer);
    this.mockMvc.perform(post("/mappingUrl/map")
            .contentType(APPLICATION_JSON_UTF8)
            .content(jString)
            .accept(MediaType.APPLICATION_JSON))                
            .andExpect(status().isOk());

}

}

and in my controller I use env like this:

@CrossOrigin
@RestController
@RequestMapping(value="/mappingUrl")
public class MappingController{ 
static Environment env = PropertiesSingleton.getInstance().env();
@RequestMapping(value="/map", method=RequestMethod.POST, 
produces="application/json")
public ResponseEntity<Object> map(@RequestBody String jsonString){
String environmentName = 
env.getProperty("Constant");
}
}

1 Answers1

0

SLF4J is probably loaded by Spring (possibly somewhere in the call stack starting with AnnotationConfigApplicationContext) but you can ignore that (and will probably disappear after setting up your test correctly).

Anyway, you need to do a few things to completely mock your singleton and return your custom environment instance. Furthermore, with this you no longer need Whitebox to update the singleton state, you just define the mock's behavior:

  1. use the specific PowerMockRunner so that the Powermock "magic" can happen
  2. suppress the static initialization sections inside the singleton
  3. mockStatic your singleton (for getInstance())
  4. define the expected behavior step by step as Powermock does not yet support DEEP STUBBING (aka environmentMock.getInstance().env())

You can find below a complete example. Please note that in my sources:

  • I've used a mock for the environment as well since you did not share MockEnvironment (it was also easier for me)
  • for brevity, I changed MappingController to make static Environment env public so I can quickly check my expected value (also you did not share the code that uses it). Please do not do this in production
package com.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.core.env.Environment;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

// use a special runner
@RunWith(PowerMockRunner.class)
// suppress static initialization block in singleton
@SuppressStaticInitializationFor("com.example.PropertiesSingleton")
public class SingletonTest {

    // create the mock to return by getInstance()
    @Mock
    private PropertiesSingleton singletonMock;

    // environment mock for quick test
    @Mock
    private Environment environmentMock;

    @Test
    public void testSomeMethod() throws Exception {
        String expectedServer = "test";
        String expectedServerPropertyName = "serverName";

        // define behavior
        // please note power mockito does not yet support DEEP STUBBING (https://github.com/powermock/powermock/issues/507), so multiple "whens" must be used
        mockStatic(PropertiesSingleton.class);
        when(PropertiesSingleton.getInstance()).thenReturn(singletonMock);
        when(singletonMock.env()).thenReturn(environmentMock);
        when(environmentMock.getProperty(expectedServerPropertyName)).thenReturn(expectedServer);

        // invoke the object under test and check results
        assertThat(MappingController.env.getProperty(expectedServerPropertyName), is(expectedServer));
    }
}

P.S. you may want to consider giving this a quick read: the benefits of constructor injection vs field injection (aka @Autowired Environment env; in your sample)

Morfic
  • 15,178
  • 3
  • 51
  • 61
  • Thank you for your response. I am still getting org.eclipse.debug.core.DebugException: com.sun.jdi.ClassNotLoadedException: Type has not been loaded occurred while retrieving component type of array. I think that could be because of the PropertiesConfig class not being mocked. I will see what I can do. If there is anything that you would recommend that is appreciated. – briefcasejoe Mar 08 '19 at 18:24
  • I really wish I could change the code to have dependency injection. The current code in the project makes me want to throw up. – briefcasejoe Mar 08 '19 at 18:28
  • @briefcasejoe Ummm `org.eclipse.debug.core.DebugException` doe not seem to be related to the project, that seems to be an IDE exception of some sort... Can you post a [sscce](http://sscce.org/) and mention exactly how you are running the test and Eclipse's version? Also what happens if you don't debug the test I wrote and simply run it? – Morfic Mar 08 '19 at 23:35
  • I am using workspace-sts 3.9.6. To answer few of your previous question. Mockenvironment I am using is from org.springframework.mock.env.MockEnvironment. While digging I realized that they are assigning to org.springframework.core.env.Environment interface from PropertiesSingleton.getInstance().env() in the controller. If i do not debug and run the test I get java.lang.ExceptionInInitializerError from PropertiesSingleton.getInstance().env() on restController. – briefcasejoe Mar 11 '19 at 13:50
  • @briefcasejoe I assume you added `mockStatic(PropertiesSingleton.class);` and saw my note on deep stubbing? If yes, then please share a complete test class that reproduces the issue. Strip any sensitive information out, rename packages, etc, but please keep the imports. We need to see how/what is configured and run. – Morfic Mar 11 '19 at 15:32
  • @briefcasejoe Try replacing `PrepareForTest` with `SuppressStaticInitializationFor` like in my sample and let me know what you get. I'm on the road now, will check back later – Morfic Mar 11 '19 at 18:42
  • still gives java.lang.ExceptionInInitializerError at the static Environment. – briefcasejoe Mar 12 '19 at 00:59
  • @briefcasejoe That is very weird, I'm fairly certain the example I posted should work just fine. Can you create a new test starting from my sample, make sure it works, then slowly add your custom pieces, and re-test until it fails again? – Morfic Mar 12 '19 at 11:24
  • So, the error is because the test is not able to initialize @PropertySources({@PropertySource("file:${catalina.home}/foo/foo.properties"), in the PropertyConfig.java file. I have no clue how I will be able to mock that in unit test. – briefcasejoe Mar 13 '19 at 19:10
  • @briefcasejoe That shouldn't even happen if you suppress the static initialization for the `PropertiesSingleton` and then use mocks. Is there any other place that would trigger a spring context to actually be loaded? – Morfic Mar 13 '19 at 19:18