简体   繁体   English

在带有PowerMocktio的Java中,如何在私有嵌套类中实例化该类的构造函数?

[英]In Java with PowerMocktio, how do you mock the constructor of a class when it's instantiated inside a private nested class?

I have a class which contains several methods I'd like to test, as well as a private nested class that is used in a few of these methods. 我有一个包含要测试的几种方法的类,以及在其中一些方法中使用的私有嵌套类。 Inside this private nested class it creates an object that attempts to form a connection to either a website or a database. 在这个私有的嵌套类中,它创建一个对象,该对象试图与网站或数据库建立连接。 I would like to seperate the tests for connecting to this outside resource and for the processing that happens to the retrieved information. 我想分开测试,以连接到该外部资源以及对检索到的信息进行的处理。 This way we can choose to not have the test environment connected to a functional 'outside' source, greatly simplifying the setup we need for these tests. 这样,我们可以选择不将测试环境连接到功能“外部”源,从而大大简化了这些测试所需的设置。

To that end I am writing a test which mocks the constructor for the object that attempts to form these connections. 为此,我正在编写一个测试,该测试模拟试图建立这些连接的对象的构造函数。 I don't want it to do anything when the nested private class attempts to form the connection, and when it tries to retrieve information I want it to just return a predefined string of data. 当嵌套的私有类尝试形成连接时,并且当它尝试检索信息时,我不希望它做任何事情,我希望它仅返回预定义的数据字符串。 At the moment I have something that looks similar to this: 目前,我有一些类似的东西:

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
        }
}

The test class looks something like this: 测试类如下所示:

@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);
    }
}

Now, I do know that inside the PrepareForTest annotation that I need to include the class that instantiates the object that I'm mocking the constructor for. 现在,我确实知道 ,在PrepareForTest批注中,我需要包括实例化要为其模拟构造函数的对象的类。 The problem is that I can't put MyClass, because that's not the object that creates it, and I can't put NestedClass, because it can't be seen by the test. 问题是我不能放MyClass,因为那不是创建它的对象,我不能放NestedClass,因为它在测试中看不到。 I have tried putting MyClass.class.getDeclaredClasses[1] to retrieve the correct class, but unfortunately PowerMocktio requires a constant to be in the annotation and this simply will not work. 我曾尝试将MyClass.class.getDeclaredClasses [1]放入正确的类中,但是不幸的是PowerMocktio需要在注释中添加一个常量,这根本行不通。

Can anyone think of a way to get this mock constructor to work? 谁能想到一种使此模拟构造函数起作用的方法?


Note: I am unable to make any alterations to the code I am testing. 注意:我无法对正在测试的代码进行任何更改。 This is because the code does work at the moment, it has been manually tested, I am writing this code so that future projects will have this automated testing framework to use. 这是因为该代码目前可以正常工作,已经过手动测试,我正在编写此代码,以便将来的项目将使用此自动测试框架。

I'm not sure if you are running your test integrated with Mockito, if you do then you can use this code: 我不确定您是否正在运行与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);
    }
}

I suspect you will have to modify the code under test. 我怀疑您将不得不修改正在测试的代码。 You have two options: 您有两种选择:

  1. Write a large integration test that covers this code so you have a safety net just in case your changes might break something. 编写涵盖此代码的大型集成测试,以便您拥有一个安全网 ,以防万一您的更改可能破坏某些东西。 This test could possibly create an in-memory database/backend and have the connector connect to that. 该测试可能会创建一个内存数据库/后端,并使连接器连接到该数据库/后端。
  2. Make a small, safe change that creates a seam for testing 进行小而安全的更改 ,以创建测试接缝

It's preferable to do both. 最好同时做两个。 See the book Working Effectively with Legacy Code for more details. 有关更多详细信息,请参见《 有效处理旧版代码 》一书。

I'll show an example of option 2. 我将显示一个选项2的示例。

First, you can create a "seam" that allows the test to be able to change the way the Connector is created: 首先,您可以创建一个“ 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();
        }

        ...
    }
}

You can then use a partial mock of MyClass to test your code. 然后,您可以使用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);
    }
}

Note that assertEquals expects the first parameter to be the expected value. 请注意, assertEquals期望第一个参数为期望值。

Also note that this test uses Mockito, not PowerMock . 另请注意,此测试使用Mockito而不是PowerMock This is good because tests that use PowerMock may be brittle and subject to breakage with small changes to the code under test. 这样做很好,因为使用PowerMock的测试可能会很脆弱,并且由于被测代码的微小更改而容易损坏。 Be warned that using partial mocks can be brittle too. 请注意,使用部分模拟也可能很脆弱。 We will fix that soon. 我们会尽快解决。

After you get the tests passing, you can refactor the code so the caller passes in a Connector factory: 通过测试后,您可以重构代码,以便调用方通过Connector工厂:

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();
        }

        ...
    }
}

The code using MyClass would preferably use a dependency injection framework like Guice or Spring. 使用MyClass的代码最好使用依赖注入框架,例如Guice或Spring。 If that isn't an option, you can make a second no-arg constructor that passes in a real ConnectorFactory . 如果这不是一个选择,则可以使第二个无参数构造函数传入真实的ConnectorFactory

Assuming the tests still pass, you can make the test less brittle by changing your test to mock ConnectorFactory instead of doing a partial mock of MyClass . 假设测试仍然通过,则可以通过将测试更改为模拟ConnectorFactory而不是对MyClass进行部分模拟来使测试不那么脆弱 When those tests pass, you can inline createConnector() . 当这些测试通过时,可以内联createConnector()

In the future, try to write tests as you write your code (or at least before spending a lot of time on manual testing). 将来,请尝试在编写代码时编写测试 (或至少在花费大量时间进行手动测试之前)。

mockConnector = mock(Connection.class); mockConnector =模拟(Connection.class);

This object is not declared. 未声明此对象。 Whatever, anyway you have to use the @Mock anotation. 无论如何,无论如何,您都必须使用@Mock注释。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM