测试需要模拟成员方法并使用PowerMockito抑制单例构造函数的静态方法

我想测试一个静态方法(例如HobbyUtil.java:shareReadContext(int lineNumber)),该方法将要求Singleton类(例如Shelf.java)获取对象(例如Book)以进行进一步检索值(例如上下文行)返回的对象。

但是,在模拟案例执行期间,该Singleton类(即Shelf.java)的ctor会触发链中的其他几个Singleton类(例如libraryA.java);并且其中之一会触发java.lang.ExceptionInInitializerError

我认为模拟/监视那些不相关的Singleton类超出了Mock Test的精神,因为它们与我的测试范围无关。

因此,我决定取消此Singleton类(即Shelf.java)的私有ctor,只让该类中的getter方法(即getBook())返回具有我喜欢的行为的模拟对象,完全符合我的测试用例。

我遇到的问题是我点击此链接成功抑制了 Shelf.java 的错误: suppress a singleton constructor in java with powermock

但是我无法弄清楚让这个getter方法返回我想要的东西(即返回一个模拟对象)的方法。

我正在使用的 PowerMockito hamcrest JUnit 的版本在此处列出以供参考:

<dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>1.7.0</version><scope>test</scope></dependency>  
<dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockit02</artifactId><version>1.7.0</version><scope>test</scope></dependency>
<dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-all</artifactId><version>1.3</version><scope>test</scope></dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>

我还用下面的示例类复制了该案例,以显示遇到的问题(HobbyUtilTest.java是我的测试案例的类):

假设应用程序中有5个Java类:

public interface Book {
    public String getcontext(int lineNumber);
}
public class Shelf {

    private static final Shelf shelf = new Shelf();
    private String BOOK_NAME = "HarryPotter";
    private Book book = null;

    private Shelf() {
        book = libraryA.getInstance().getBook(BOOK_NAME);
        // many relatively complicated logics are here in actual class ... 
        // I want to suppress this ctor 
    }

    public static Shelf getInstance() {
        return shelf;
    }

    public Book getBook() {
        return book;  // I want to return my mocked object while calling this method in test case
    }

}
public class HobbyUtil {

    public static String shareReadContext(int lineNumber){
        String context = "";
        Book book = Shelf.getInstance().getBook();

        for (int i = 0 ; i < lineNumber; i++) {
            context += book.getcontext(i);
        }
        return context;
    }

    private HobbyUtil() {}
}
public class libraryA {
    private static final libraryA library = new libraryA();
    private Book book;

    private libraryA() {
        throw new java.lang.ExceptionInInitializerError();
    }

    public static libraryA getInstance() {
        return library;
    }

    public Book getBook(String bookName) {
        return book;
    }
}
public class BookImpl implements Book{

    private String bookName = null;

    BookImpl(String bookName){
        this.bookName = bookName;
    }

    @Override
    public String getcontext(int lineNumber) {
        return lineNumber + ": Context";
    }
}

然后我将使用shareReadContext(int lineNumber)HobbyUtil.java中测试静态方法HobbyUtilTest.java

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.powermock.api.mockito.PowerMockito.when;

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

@RunWith(PowerMockRunner.class)
@PrepareForTest(value = { Shelf.class })
public class HobbyUtilTest {

    @Test
    public void testShareReadContext() throws Exception {

        Book mockBook = PowerMockito.mock(Book.class);
        when(mockBook.getcontext(anyInt())).thenReturn("context for test");

        PowerMockito.suppress(PowerMockito.constructor(Shelf.class));
        //PowerMockito.spy(Shelf.class);
        //when(Shelf.getInstance().getBook()).thenReturn(mockBook); // does not work
        //PowerMockito.doReturn(mockBook).when(Shelf.getInstance().getBook()); // does not work
        //PowerMockito.when(Shelf.class,"getBook").thenReturn(mockBook); // does not work
        //TODO any approach for it?

        String context = HobbyUtil.shareReadContext(1);
        assertThat(context,is("context for test"));
    }
}

有人可以帮助建议我如何让Book book = Shelf.getInstance().getBook();中的行HobbyUtil.java返回我的模拟对象(即mockBook)吗?

YJX690271813 回答:测试需要模拟成员方法并使用PowerMockito抑制单例构造函数的静态方法

假设您的Shelf类位于包test中,则此方法应该有效:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("test.Shelf")
public class HobbyUtilTest {

    @Test
    public void testShareReadContext() throws Exception {

        Book book = Mockito.mock(Book.class);
        Mockito.when(book.getContext(Mockito.anyInt())).thenReturn("context for test");

        Shelf shelf = Mockito.mock(Shelf.class);
        Mockito.when(shelf.getBook()).thenReturn(book);

        PowerMockito.mockStatic(Shelf.class);
        PowerMockito.when(Shelf.getInstance()).thenReturn(shelf);

        String context = HobbyUtil.shareReadContext(1);
        Assert.assertEquals("context for test",context);
    }
}

您将要禁止Shelf(不仅是构造函数)的字段的初始化,然后只需为Shelf.getInstance()(及后续)方法定义适当的模拟即可。


Ps。:

@SuppressStaticInitializationFor似乎等同于:

@RunWith(PowerMockRunner.class)
@PrepareForTest(Shelf.class)
public class HobbyUtilTest {

    @Test
    public void testShareReadContext() throws Exception {

        PowerMockito.suppress(PowerMockito.fields(Shelf.class));
        PowerMockito.suppress(PowerMockito.constructor(Shelf.class));       

        // ...
    }
}
,

在再次搜索其他资源之后,让我在此处发布替代方法作为参考。

在引用此站点后: https://blog.jayway.com/2009/10/28/untestable-code-with-mockito-and-powermock/

我发现,通过将以下代码行添加到原始测试用例的TODO部分中,也可以帮助达到目的:

  

PowerMockito.stub(PowerMockito.method(Shelf.class,   “ getBook”))。toReturn(mockBook);

这种方法的缺点是方法名称需要硬编码为String值,IDE重命名自动化功能在这种情况下将无效。

本文链接:https://www.f2er.com/3115902.html

大家都在问