Error in junit while mocking

2019-04-12 04:29发布

问题:

I am new to Junit,Below is the junit code which i am running.

package com.de.base.util.general;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.api.mockito.PowerMockito;

@RunWith(MockitoJUnitRunner.class)
public class JReportUtilTest {
@InjectMocks 
ReportUtil w_res = new ReportUtil();

@Mock
CollectionUtil w_util;

@Test
public void test_removeHashedSettings() throws Exception {
    HashMap<String ,String> w_abc = new HashMap<String,String>();
    w_abc.put("abc","89");
    //CollectionUtil mock = org.mockito.Mockito.mock(CollectionUtil.class);
    //PowerMockito.mockStatic(CollectionUtil.class,w_abc);          
    when(w_util.createHashMap("abc:89", ":")).thenReturn(w_abc);
    assertEquals("abc:89:",ReportUtil.removeHashedSettings("1", "abc:89", ":"));
}
}

Here is my api removedHashedSettingsin ReportUtil

public static String removeHashedSettings(String key, String a_settings, String deilimiter) throws Exception
    {
        if (!(key != null && key.trim().length() > 0))
            return a_settings;
        if (!(a_settings != null && a_settings.trim().length() > 0))
            return a_settings;

        HashMap hSettings = CollectionUtil.createHashMap(a_settings, deilimiter);
        hSettings.remove(key);
        return getSettingFromHash(hSettings, deilimiter);
    }

Below is the code for createHashMap in CollectionUtil which i have to mock.

public static HashMap<String, String> createHashMap(String a_NameValStr, String a_Delim)// throws Exception
    {
        HashMap<String, String> w_KeyVal = new HashMap<String, String>();
        if (LOGGER.isInfoEnabled()) LOGGER.info("CollectionUtil:createHashMap:Hashing string: "+ a_NameValStr );

        if(a_NameValStr == null) return w_KeyVal;

            StringTokenizer w_StrTkn = new StringTokenizer(a_NameValStr, a_Delim);
            if( w_StrTkn.countTokens() == 0 || (w_StrTkn.countTokens()%2) != 0 )
            {
                LOGGER.warn("CollectionUtil:createHashMap:Invalid number of tokens to hash: "+ a_NameValStr+":"+w_StrTkn.countTokens() );
                return w_KeyVal;
            }

            while (w_StrTkn.hasMoreTokens()) w_KeyVal.put( w_StrTkn.nextToken(), w_StrTkn.nextToken());
        System.out.println(w_KeyVal);   
        return w_KeyVal;
    }

Here is the error i am getting while running my junit test case.

 org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

    at com.de.base.util.general.JReportUtilTest.test_removeHashedSettings(JReportUtilTest.java:32)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

I am using mockito -all-1.10.19.jar,powermock-api-mockito-1.6.6.jar,powermock-core-1.6.6.jar,powermock-module-junit4-1.6.6.jar Can anyone help me to solve this problem?

回答1:

Here is my working code:

import static org.junit.Assert.assertEquals;

import java.util.HashMap;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(CollectionUtil.class)
public class TestHarnesTest {
    @InjectMocks
    TestHarnes w_res = new TestHarnes();

    @Before
    public void before() {
        PowerMockito.mockStatic(CollectionUtil.class);
    }

    @Test
    public void test_removeHashedSettings() throws Exception {
        HashMap<String, String> w_abc = new HashMap<String, String>();
        w_abc.put("abc", "89");
        // CollectionUtil mock = org.mockito.Mockito.mock(CollectionUtil.class);
        // PowerMockito.mockStatic(CollectionUtil.class,w_abc);
        PowerMockito.when(CollectionUtil.createHashMap(Mockito.eq("abc:89"), Mockito.eq(":"))).thenReturn(w_abc);
        assertEquals("abc:89:", TestHarnes.removeHashedSettings("1", "abc:89", ":"));
    }
}

and the TestHarnes class

public class TestHarnes {

    public static String removeHashedSettings(final String key, final String a_settings, final String deilimiter) throws Exception {
        if (!(key != null && key.trim().length() > 0)) {
            return a_settings;
        }
        if (!(a_settings != null && a_settings.trim().length() > 0)) {
            return a_settings;
        }

        HashMap hSettings = CollectionUtil.createHashMap(a_settings, deilimiter);
        hSettings.remove(key);
        return getSettingFromHash(hSettings, deilimiter);
    }

    private static String getSettingFromHash(final HashMap hSettings, final String deilimiter) {
        return "";
    }

}


回答2:

You don't use the PowerMock runner :

@RunWith(PowerMockRunner.class)

Mockito cannot mock static method but PowerMock does.

And you should mock the class with static method :

PowerMockito.mockStatic(CollectionUtil.class);

Anyway a better design is replacing the static method by an instance method.
Static methods are not naturally testable and force to create complex and not readable workaround.
For example, look at the complexity of the mix of dependencies required to test a simple class.

You should keep the use of static methods as helper that don't perform core logic of the domain and that don't need to be mocked.

When the methods perform core logic of the domain as it is the case for the createHashMap() static method, you very probably need to mock it to not create side effects between dependent classes during the tests.
And as you have noticed : mocking static methods is really clumsy as static methods are really not designed to be overriden.

Besides, for core logic of the domain, you should have the ability to take advantage of the OOP (inheritancy, polymorphism, design patterns, etc... ), how to achieve it with static methods ? –

For legacy code we cannot really not change, it is acceptable but otherwise, no it doesn't and you should refactor your code.