在使用PowerMocktio的Java中,当在类私有嵌套类中实例化时,如何模拟类的构造函数?

时间:2015-06-18 09:39:32

标签: java unit-testing junit mockito powermock

我有一个类,它包含我想测试的几个方法,以及在其中一些方法中使用的私有嵌套类。在这个私有嵌套类中,它创建了一个试图与网站或数据库建立连接的对象。我想分开连接到这个外部资源的测试以及检索到的信息所发生的处理。这样我们就可以选择不将测试环境连接到功能“外部”源,从而大大简化了这些测试所需的设置。

为此,我正在编写一个测试,它会尝试构造这些连接的对象的构造函数。当嵌套的私有类尝试形成连接时,我不希望它做任何事情,当它试图检索信息时,我希望它只返回一个预定义的数据字符串。目前我的东西看起来与此相似:

public class MyClass {

    public int mainMethod() {
        //Some instructions...

        NestedClass nestedClass = new NestedClass();
        int finalResult = nestedClass.collectAndRefineData();
    }

    private class NestedClass {

        public NestedClass() {
            Connector connect = new Connector();
        }

        public int collectAndRefineData() {
            //Connects to the outside resource, like a website or database

            //Processes and refines data into a state I want

            //Returns data
        }
}

测试类看起来像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Connector.class})
public class MyClassTests {

    @Test
    public void testOne() {
        mockConnector = mock(Connection.class);
        PowerMockito.whenNew(Connector.class).withNoArguments().thenReturn(mockConnector);

        MyClass testClass = new MyClass();
        int result = testClass.mainMethod();

        Assert.equals(result, 1);
    }
}

现在,我确实知道在PrepareForTest注释中,我需要包含实例化我正在嘲笑构造函数的对象的类。问题是我不能放MyClass,因为那不是创建它的对象,我不能放置NestedClass,因为测试无法看到它。我已经尝试使用MyClass.class.getDeclaredClasses [1]来检索正确的类,但不幸的是,PowerMocktio需要一个常量才能在注释中,这根本不起作用。

有人能想出让这个模拟构造函数起作用的方法吗?

注意:我无法对我正在测试的代码进行任何更改。这是因为代码目前正常工作,它已经过手动测试,我正在编写这段代码,以便将来的项目可以使用这个自动化测试框架。

3 个答案:

答案 0 :(得分:0)

我不确定您是否正在运行与Mockito集成的测试,如果您这样做,则可以使用此代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Connector.class})
public class MyClassTests {

    @Mock
    Connector mockConnector;

    @InjectMocks
    MyClass testClass;

    @Test
    public void testOne() {
        PowerMockito.whenNew(Connector.class).withNoArguments().thenReturn(mockConnector);

        int result = testClass.mainMethod();

        Assert.equals(result, 1);
    }
}

答案 1 :(得分:0)

我怀疑你必须修改测试中的代码。您有两种选择:

  1. 编写一个涵盖此代码的大型集成测试,以便拥有安全网,以防万一您的更改可能会破坏某些内容。此测试可能会创建内存数据库/后端,并使连接器连接到该。
  2. 进行小巧,安全的更改,为测试创建接缝
  3. 最好同时做到这两点。有关详细信息,请参阅书籍Working Effectively with Legacy Code

    我将展示选项2的示例。

    首先,你可以创建一个" seam"这允许测试能够改变Connector的创建方式:

    public class MyClass {
    
        public int mainMethod() {
            // Some instructions...
    
            NestedClass nestedClass = new NestedClass();
            return nestedClass.CollectAndRefineData();
        }
    
        // visible for testing
        Connector createConnector() {
            return new Connector();
        }
    
        private class NestedClass {
            private final Connector connector;
    
            public NestedClass() {
                connector = createConnector();
            }
    
            ...
        }
    }
    

    然后,您可以使用MyClass @RunWith(JUnit4.class) public class MyClassTests { @Test public void testOne() { MyClass testClass = spy(MyClass.class); Connector mockConnector = mock(Connector.class); when(testClass.createConnection()) .thenReturn(mockConnector); int result = testClass.mainMethod(); Assert.assertEquals(1, result); } } 来测试您的代码。

    assertEquals

    请注意,Connector期望第一个参数为预期值。

    另请注意,此测试使用的是Mockito,而不是PowerMock 。这很好,因为使用PowerMock的测试可能很脆弱,并且在测试代码发生微小变化时会出现破坏。请注意,使用部分嘲笑也可能很脆弱。我们很快就会解决这个问题。

    通过测试后,您可以重构代码,以便调用者通过public class MyClass { private final ConnectorFactory connectorFactory; @Inject MyClass(ConnectorFactory factory) { this.connectorFactory = factory; } // visible for testing Connector createConnector() { return connectorFactory.create(); } private class NestedClass { private final Connector connector; public NestedClass() { connector = createConnector(); } ... } } 工厂:

    MyClass

    使用ConnectorFactory的代码最好使用像Guice或Spring这样的依赖注入框架。如果这不是一个选项,你可以创建一个传递真实ConnectorFactory的第二个无参数构造函数。

    假设测试仍然通过,您可以通过将测试更改为模拟MyClass而不是对createConnector()进行部分模拟来使测试不那么脆弱。当这些测试通过时,您可以内联var data = [ {"id" : 10, "name" : "Name 1", "city" : "San Francisco"}, {"id" : 12, "name" : "Name 2", "city" : "San Diego"}, {"id" : 20, "name" : "Name 3", "city" : "Santa Barbara"}, ];

    将来,在编写代码时尝试编写测试(或至少在花费大量时间进行手动测试之前)。

答案 2 :(得分:-1)

mockConnector = mock(Connection.class);

未声明此对象。无论如何,无论如何你必须使用@Mock anotation。