2010-09-17 2 views
4

Я тестирую метод сервлета doPost() с использованием объектов EasyMock для аргументов HttpServletRequest и HttpServletResponse. В методе doPost(), который я тестирую, объекты запроса и ответа используются в качестве аргументов для класса статических методов для другого класса, и я хочу игнорировать (т. Е. Не записывать как ожидалось) любые вызовы, сделанные на объектах запроса и ответа в этом методе (это не относится к этому тесту). Например doPost() метод класса сервлета я тестирую выглядит так:Как отключить запись для объекта EasyMock?

@Override 
protected void doPost(final HttpServletRequest servletRequest, 
         final HttpServletResponse servletResponse) 
    throws ServletException, IOException 
{ 
    // handle an "updateFolder" event 
    String eventParameter = servletRequest.getParameter("event"); 
    if ("updateFolder".equalsIgnoreCase(eventParameter)) 
    { 
     // update the news documents folder settings 
     String folderId = servletRequest.getParameter("folderId"); 
     IPortletContext portletContext = PortletContextFactory.createPortletContext(servletRequest, servletResponse); 
     IPortletResponse portletResponse = portletContext.getResponse(); 
     portletResponse.setSettingValue(SettingType.CommunityPortlet, "NEWS_DOCUMENTS_FOLDER_ID", folderId); 
    } 

    // redirect to the appropriate URL 
    servletResponse.sendRedirect(redirectUrl); 
} 

Когда вышеприведенный код попадает на стадию, где PortletContextFactory.createPortletContext() называется я на самом деле не волнует, какой метод вызовы делаются по требованию и объекты ответа в этом методе, но если я передаю объекты mock request и response при тестировании этого метода, я получаю ошибки от EasyMock, говорящие мне, что отсутствуют определения поведения. Например, у меня есть тестовый метод, который выглядит следующим образом:

@Test 
public void testPostWithUpdate() 
    throws Exception 
{ 
    // create mock objects and record their expected calls 
    HttpServletRequest mockServletRequest = createMock(HttpServletRequest.class); 
    HttpServletResponse mockServletResponse = createMock(HttpServletResponse.class); 
    IPortletResponse mockPortletResponse = createMock(IPortletResponse.class); 
    IPortletContext mockPortletContext = createMock(IPortletContext.class); 
    expect(mockServletRequest.getContextPath()).andReturn(null); 
    expect(mockServletRequest.getParameter("event")).andReturn("updateFolder"); 
    expect(mockServletRequest.getParameter("folderId")).andReturn(null); 
    expect(PortletContextFactory.createPortletContext(mockServletRequest, mockServletResponse)).andReturn(mockPortletContext); 
    expect(mockPortletContext.getResponse()).andReturn(mockPortletResponse); 
    mockPortletResponse.setSettingValue(SettingType.CommunityPortlet, "NEWS_DOCUMENTS_FOLDER_ID", null); 
    mockServletResponse.sendRedirect(EasyMock.anyObject(String.class)); 

    // take the mock objects out of record state 
    replay(mockPortletContext, mockPortletResponse, mockServletRequest, mockServletResponse); 

    // instantiate an object of the class and run the method we want to test 
    ControllerServlet controllerServlet = new ControllerServlet(); 
    controllerServlet.doPost(mockServletRequest, mockServletResponse); 

    // verify that our mocks behaved as expected 
    verify(mockPortletContext, mockPortletResponse, mockServletRequest, mockServletResponse); 
} 

я получаю следующее сообщение об ошибке, когда я запускаю тестовый класс:

com.plumtree.openfoundation.util.XPIllegalStateException: missing behavior definition for the preceding method call getCharacterEncoding() 
    at com.plumtree.openfoundation.util.XPException.GetInstance(XPException.java:397) 
    at com.plumtree.openfoundation.util.XPException.GetInstance(XPException.java:350) 
    at com.plumtree.openfoundation.web.XPRequest.InitRequest(XPRequest.java:201) 
    at com.plumtree.openfoundation.web.XPRequest.<init>(XPRequest.java:111) 
    at com.plumtree.remote.portlet.PortletContextFactory.createPortletContext(PortletContextFactory.java:32) 
    at com.abc.servlet.ControllerServletTest.testPostWithUpdate(ControllerServletTest.java:31) 
    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:44) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236) 
    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) 
Caused by: java.lang.IllegalStateException: missing behavior definition for the preceding method call getCharacterEncoding() 
    at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43) 
    at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:73) 
    at $Proxy4.setCharacterEncoding(Unknown Source) 
    at com.plumtree.openfoundation.web.XPRequest.InitRequest(XPRequest.java:135) 
    ... 25 more 

Я полагаю, что ошибки выше вызваны не записывая вызовы методов, выполненные в методе PortletContextFactory.createPortletContext() на объектах запроса и ответа, переданных в качестве аргументов. Если это на самом деле то, что происходит здесь, то как я могу переделать вещи так, чтобы вызовы методов, выполненные по методам запроса и ответа методом PortletContextFactory.createPortletContext(), не учитывались?

ответ

1

Попробуйте Mockito http://mockito.org/.

Это намного проще в использовании от EasyMock и не заставляет вас кодировать все вызовы методов.

+1

Также нет EasyMock - для этого есть _nice mocks_. И простота использования - это в значительной степени вопрос личного вкуса и опыта. –

+0

@Peter Я работал с обоими, и теперь я предпочитаю Mockito. Это может быть вопросом вкуса, тем не менее IMHO Mockito имеет опрятный API. Другими словами, Mockito - хороший макет;) –

1

Возможно, вам нужно что-то вроде этого:

expect(mockServletRequest.getCharacterEncoding()).andReturn("UTF-8"); 

Или использовать createNiceMock(), как предполагает Петера Török.

+0

Возможно, вы столкнетесь с другими методами, которые не выполняются вашим макетом, поэтому createNiceMock() может работать, если null не вызывает других ошибок. – jbindel

+0

Да, это то, чего я пытаюсь избежать, так как я не уверен, какие вызовы методов выполняются по ложному запросу в методе стороннего класса, и это не имеет никакого отношения, так как я не тестирую этот класс , Если реализация стороннего класса изменится, мой тест может нарушиться, используя описанный выше подход. Например, похоже, что PortletContextFactory.createPortletContext() вызывает метод getCharacterEncoding() в mock-запросе, и если реализация этого метода изменится, мой тест завершится неудачно, если у меня есть это ожидание на макет-объекте. –

1

Вам необходимо высмеять PortletContextFactory.createPortletContext позвонить. Сам EasyMock не поддерживает статический метод издевательств, однако расширение PowerMock для EasyMock делает. Вот пример кода, вы должны вставить в ваш тест:

mockStatic(PortletContextFactory.class);  
expect(PortletContextFactory.createPortletContext(mockServletRequest, mockServletResponse)).andReturn(mockPortletContext); 
replay(PortletContextFactory.class); 

Есть также 2 Требования:

  1. Используйте @RunWith(PowerMockRunner.class) аннотацию на уровне класса в теста.
  2. Используйте аннотацию @PrepareForTest(PortletContextFactory.class) на уровне класса тестового примера.

Узнайте больше на сайте: http://code.google.com/p/powermock/wiki/MockStatic

+0

Да, PowerMock позволяет вам это делать, но этот подход следует использовать только с устаревшим кодом, и использование Powermock для тестирования классов во время разработки, безусловно, является признаком плохого дизайна. –

+0

@ Pierre-Henri Да, использование и издевательство над статическими методами следует избегать всеми способами. Однако _OP_ явно не упоминал в своем вопросе, тестирует ли он устаревший код или разрабатывает новый (или, по крайней мере, имеет разрешение на его модификацию). – Idolon

1

альтернативный подход для тестирования в подобных ситуациях:

class Class_Under_Test { 
    public void A() { 
    B b = C.create(); //create is static 
    D d = e.(b); 
    } 
} 

Чтобы обойти статической опорной проблемы в EasyMock вы можете изменить определение метода для:

@VisibleForTesting 
B create() { 
    return C.create(); 
} 

public A() { 
    B b = create(); 
    D d = e.(b); 
} 

В вашем тестовом классе вы можете сделать это:

Class testSomething { 

    private Mock_Class_Under_Test mockOject;//Class Defined below 
    private B mockB; 

    @Override 
    void setup() { 
    mockB =createMock(B.class); 
    } 

    private class Mock_Class_Under_Test extends Class_Under_Test { 

     @Override 
     B create() { 
     return mockB; 
     } 
    } 

    public void testA() { 
    //No need to put expectation on static call as create() will always return mock. 
    expect(e.something(mockB)).andReturn(somethingElse); 
    } 
} 

Все ваши дальнейшие тесты будут использовать mockObject для вызова методов класса Class_Under_Test.

1

Чтобы превратить запись, используйте красивый макет. Из документации:

На фиктивном объекте, возвращенный createMock() поведения по умолчанию для всех методов бросить AssertionError для всех неожиданных вызовов методов. Если вы хотите «хороший» Mock Object, который по умолчанию позволяет всем методам вызывать и возвращает соответствующие пустые значения (0, null или false), вместо createNiceMock().

Кроме того, в общем, что вы хотите высмеять, это createPortletContext(servletRequest, servletResponse). К сожалению, это статический звонок. Чтобы достичь этого макета, создайте свой собственный завод, который вернет portletContext, и передайте эту фабрику вам испытанному классу (желательно в конструкторе). Отметьте эту фабрику и portletContext тоже, так что вы можете проверить только то, что имеет значение здесь: код, который вы написали.

+1

Отдельный завод для 'portletContext' - хороший подход. Однако @OP не сможет передать его конструктору, так как он тестирует сервлет (для которого требуется открытый конструктор no-args).Но можно создать сеттер в сервлете и вызвать его в тестовом случае, чтобы передать издеваемую версию фабрики (очевидно, заводская реализация по умолчанию должна быть установлена ​​в конструкторе по умолчанию сервлета) – Idolon

+0

Хорошая мысль о конструкторе по умолчанию :) –

+0

Наилучшим подходом к этой проблеме является, на мой взгляд, делегирование вызова doPost для плана java-сервиса, с которым вы можете иметь полный контроль. Это может быть излишним, поэтому подход @Idolon для использования сеттера также является вполне допустимым. –

Смежные вопросы