Использование JMockit для возврата фактического экземпляра из макетного конструктора

Я рассмотрел следующий вопрос, и он не совпадает с моим:

jMockit: как ожидать вызовов конструктора для объектов Mocked?

Этот вопрос похож, но ответ мне не помогает:

Как смоделировать конструктор по умолчанию для Класс свиданий с JMockit?

То, что я пытаюсь сделать, это издеваться над вызовом конструктора java.util.zip.ZipFile, в частности, с аргументом java.io.File. Я хотел бы, чтобы конструктор возвращал экземпляр другого ZipFile, экземпляр которого я создам с помощью конструктора, который принимает только аргумент String.

Этот вызов конструктора происходит внутри тестируемого метода, поэтому я не могу внедрить ZipFile, который хочу, в качестве параметра.

Например, код выглядит примерно так:

public void whatever() {
   //some code
   //some more code
   foo();
   //yet more unrelated code
}

private Blah foo() {
    ZipFile zf;
    //a bunch of code we don't care about

    zf = new ZipFile(someFile);// I want to give it a known zipfile! mock this!


    // some more code we don't care about

    Enumeration<?> entries = zf.entries();
    ZipEntry entry = (ZipEntry) entries.nextElement();
    InputStream is = zf.getInputStream(entry)
    //maybe some other calls to the ZipFile

    // do something else
}

Моя первая мысль состояла в том, чтобы сделать следующее со статическим частичным издевательством:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    @Mocked("(java.io.File)")
    ZipFile zf;
    {
        new ZipFile((File) any); result = test;
    }
};

Но это не будет работать, как указано в этой строке в руководстве: constructors have void return type, so it makes no sense to record return values for them

Второй моей мыслью было попробовать следующее:

new NonStrictExpectations() {
    {
        newInstance("java.util.zip.ZipFile", new File("path/to/actual.zip"));
    }
};

Но это выдает следующее при попытке инициализировать файл:

java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(Unknown Source)
at java.util.zip.ZipFile.<init>(Unknown Source)

Моей третьей мыслью было использовать @MockClass, как показано ниже:

@Before
public void setUp() throws Exception {
    Mockit.setUpMocks(MockedZipFile.class);
}
@After
public void tearDown() {
    Mockit.tearDownMocks();
}

@MockClass(realClass=ZipFile.class)
public static class MockedZipFile {
    public ZipFile it;
    @Mock
    public void $init(File f) throws ZipException, IOException {
        it = new ZipFile("path/to/actual.zip");//this is what would be called
    }
}

Но это шланги некоторых других издевательств, которые у меня есть, которые загружают файл конфигурации для другой части моего тестового класса. Не говоря уже о том, что мне понадобятся разные zip-файлы для разных тестовых случаев.

Я полагаю, что мог бы издеваться над всем, что делает ZipFile, но это быстро станет огромной проблемой, так как это называется во многих местах, его вывод нужно будет имитировать и т. д., и т. д. Рефакторинг, чтобы попытаться сделать это доступным было бы неудобно, поскольку код, использующий ZipFile, является внутренним по отношению к коду, и общедоступные методы на самом деле не заботятся об этом.

У меня есть ощущение, что JMockit может разрешить это (предоставляя конкретный экземпляр объекта при вызове конструктора), но я не могу этого понять. У кого-нибудь есть идеи?

РЕДАКТИРОВАТЬ: я попробовал метод, предложенный @Rogerio, но у меня появилась новая ошибка. Вот моя установка:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    ZipFile zf;
    {
        zf.entries();
        result = test.entries();
        zf.getInputStream((ZipEntry) any);
        result = new Delegate() {
            InputStream getInputStream(ZipEntry entry) throws IOException {                 
                return test.getInputStream(entry);
            }
        };
    }
};

но я получаю следующую трассировку стека:

java.lang.InternalError
at path.to.test.ExtractDataTest$1.<init>(ExtractDataTest.java:61)
at path.to.test.ExtractDataTest.setUp(ExtractDataTest.java:61)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

где строка 61 — это строка new NonStrictExpectations() {.

Я действительно хочу сказать: «Вместо того, чтобы издеваться над этим объектом, замените этот другой объект того же типа». Может я плохо выразился.

EDIT2: я решил, что должен указать номера версий: Использование Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10


person Kane    schedule 05.12.2011    source источник


Ответы (2)


JMockit может имитировать класс ZipFile, но он мешает загрузке класса, поскольку подкласс JarFile используется JVM все время (всякий раз, когда он загружает класс из файла jar в пути к классам). В настоящее время нет простого способа избежать этого вмешательства (есть план «исправить» это, но это займет время).

Тем не менее, этот конкретный тестовый пример в любом случае не очень подходит для инструмента для имитации. Вместо этого я бы рекомендовал настроить тест так, чтобы он предоставлял реальный zip-файл с желаемым содержимым в нужном месте.

(еще одно редактирование) Я только что применил изменение к JMockit (для версии 0.999.12), которое позволяет пройти следующий тест, при условии, что в рабочем каталоге есть файл test.zip, и он содержит текстовый файл, первая строка которого "тест" :

@Test
public void mockZipFile() throws Exception
{
    final ZipFile testZip = new ZipFile("test.zip");

    new NonStrictExpectations() {
        @Capturing @Injectable ZipFile mock;

        {
            mock.entries(); result = testZip.entries();

            mock.getInputStream((ZipEntry) any);
            result = new Delegate() {
                InputStream delegate(ZipEntry e) throws IOException {
                    return testZip.getInputStream(e);
                }
            };
        }
    };

    ZipFile zf = new ZipFile("non-existing");
    ZipEntry firstEntry = zf.entries().nextElement();
    InputStream content = zf.getInputStream(firstEntry);
    String textContent = new BufferedReader(new InputStreamReader(content)).readLine();

    assertEquals("test", textContent);
}

Тем не менее, я бы по-прежнему рекомендовал не использовать фиктивный API для подобных случаев. Вместо этого используйте реальный файл.

person Rogério    schedule 06.12.2011
comment
К сожалению, я получил ошибку, пытаясь использовать этот метод. Я обновил свой вопрос с кодом и трассировкой стека. - person Kane; 06.12.2011
comment
Вы были правы, это не сработало из-за помех при загрузке класса. Я переписал свой ответ и предложил другое решение, без насмешек. - person Rogério; 06.12.2011
comment
Ваша рекомендация как раз о том, о чем вопрос! Я хочу предоставить фактический zip-файл, я просто хочу заставить конструктор предоставить доступ к тестовому файлу (который может отличаться для разных тестовых случаев). Как я отметил в вопросе, это внутренняя логика, и ее рефакторинг был бы беспорядком. Я полагаю, что мог бы выделить половину внутренней логики в отдельный класс, но это похоже на беспорядок. Я также мог бы создать метод для получения файла и возврата zip-файла, но было бы неправильным превращать однострочный метод в метод только для тестирования. Возможно, мой дизайн шаткий. - person Kane; 06.12.2011
comment
Я полагаю, что суть проблемы в том, что если у вас есть объект с локальной областью видимости, и вы хотите заменить этот объект другим объектом, можно ли это сделать с помощью насмешек? - person Kane; 06.12.2011
comment
Спасибо за обновления. В итоге я создал метод, который инкапсулирует конструктор, и издевался над ним, чтобы я мог вернуть фактический файл, как вы предлагаете. - person Kane; 08.12.2011

Это, вероятно, вам не поможет, но если вы используете Mockito или EasyMock, вы можете добавить PowerMock, который позволяет вам имитировать создание новых объектов в тестируемом коде.

person David M. Karr    schedule 06.12.2011
comment
Нет, мне не помогает. Спасибо, в любом случае. (Причина не в том, что эти фреймворки мешают нашему инструменту покрытия кода, поэтому мы не сможем их использовать. Не то чтобы я хотел переключить всю нашу фреймворк тестирования только для одной проблемы, с которой я столкнулся.) - person Kane; 06.12.2011