Pregunta

I'm pretty new to Mockito and have some trouble with clean up.

I used to use JMock2 for unit tests. As far as I know, JMock2 preserves the expectations and other mock information in a context which will be rebuilt for every test method. Therefore every test method is not interfered by the others.

I adopted the same strategy for spring tests when using JMock2, I found a potential problem with the strategies I used in my post: The application context is rebuilt for every test method and therefore slows the whole test procedure.

I noticed many articles recommend using Mockito in spring tests and I would like to have a try. It works well until I write two test method in a test case. Each test method passed when it ran alone, One of them failed if they ran together. I speculated that this is because the mock infomation was preserved in the mock itself('cause I don't see any context object like that in JMock) and the mock(and the application context) is shared in both test methods.

I solved it by adding reset() in @Before method. My question is what is the best practice to handle this situation (The javadoc of reset() says that the code is smell if you need reset())? Any idea is appreciate, thanks in advance.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "file:src/main/webapp/WEB-INF/booking-servlet.xml",
    "classpath:test-booking-servlet.xml" })
@WebAppConfiguration
public class PlaceOrderControllerIntegrationTests implements IntegrationTests {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Autowired
private PlaceOrderService placeOrderService;

@Before
public void setup() {
    this.mockMvc = webAppContextSetup(this.wac).build();

    reset(placeOrderService);// reset mock
}

@Test
public void fowardsToFoodSelectionViewAfterPendingOrderIsPlaced()
        throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenReturn(pendingOrder);

    mockMvc.perform(...);

}

@Test
public void returnsToPlaceOrderViewWhenFailsToPlaceOrder() throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    NoAvailableRestaurantException noAvailableRestaurantException = new NoAvailableRestaurantException(
            deliveryAddress, with(deliveryTime));
    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenThrow(noAvailableRestaurantException);

            mockMvc.perform(...);

}
¿Fue útil?

Solución

  1. About placing the reset after the test method

    I think reseting the mocks should better be done after the test method, as it implies there is indeed something that happened during the test that need to be cleaned.

    If the reset is done before the test method, I would feel unsure, what the heck happened before the test that should be reseted? What about about non-mocks object? Is there a reason (maybe there is) for it? If there's a reason why it's not mentioned in the code (for example the method name)? Et cetera.

  2. Not a fan of Spring based tests

    1. Background

      Using Spring is like giving up on unit testing a class ; with Spring you have less control on the test : isolation, instantiation, lifecycle, to cite a few of the looked property in a unit test. However in many cases Spring offer libraries and framework that are not that "transparent", for testing you better test the actual behavior of the whole stuff, like with Spring MVC, Spring Batch, etc.

      And crafting these tests is much more troublesome as it imposes in many cases the developer to craft integration tests to seriously test the behavior of the production code. As many developer don't know every detail about how the your code lives inside Spring, this can lead to many surprises to try to test a class with unit tests.

      But trouble continues, tests should be fast and small to give fast feedback to the developers (IDE plugin such as Infinitest are great for that), but tests with Spring are inherently more slow and more memory consuming. Which tends to run them less often and even avoid them totally on the local workstation ...to later discover on the CI server that they fail.

    2. Lifecycle with Mockito and Spring

      So when an integration test is crafted for a subsystem, you end up with many objects and obviously the collaborators, that are probably mocked. The lifecycle is controlled by the Spring Runner, but Mockito mocks are not. So you have to manage the mocks lifecycle yourself.

      Again about lifecycle during a project with Spring Batch we had some issues with residual effects on non mocks so we had two choices, make only one test method per test class or use the dirty context trick : @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD). This lead to slower tests, higher memory consumption, but it was the best option we had. With this trick you won't have to reset Mockito mocks.

  3. A possible light in the dark

    I don't know well enough the project, but springockito can provide you some sugar about lifecycle. The annotation subproject seems to be even better : it seems to let Spring manage the lifecycle of the beans in the Spring container and to let the test control how mocks are being used. Still I have no experience with this tool, so there might be surprises.

As a disclaimer, I like Spring a lot, it offers many remarkable tool to simplify other framework usage, it can augment productivity, it helps the design, but like every tool the Humans invented there's always a rough edge (if not more...).

On a side note, this is interesting to see this problematic happen to be in a JUnit context, as JUnit instantiate the test class for each test method. If the test were based on TestNG then the approach might be a little different as TestNG creates only one instance of the test class, resting mock fields would be mandatory regardless of using Spring.


Old answer:

I'm not a huge fan of using Mockito mocks in a spring conxtext. But could you be looking for something like :

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}

Otros consejos

Spring Boot has @MockBean annotation which you can use to mock your service. You don't need to reset mocks manually any more. Just replace @Autowired by @MockBean:

@MockBean
private PlaceOrderService placeOrderService;

Instead of injecting the placeOrderService object, you should probably just let Mockito initialize it as a @Mock before each test, via something like this:

@Mock private PlaceOrderService placeOrderService;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
}

As recommended in the Javadoc here: http://docs.mockito.googlecode.com/hg/latest/org/mockito/MockitoAnnotations.html

You can even put the @Before method in a superclass and just extend it for each test case class that uses @Mock objects.

Spring based test is hard to make fast and independed (as @Brice wrote). Here is a litle utility method for reset all mocks (you have to call it manually in every @Before method):

import org.mockito.Mockito;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;


public class MyTest {
    public void resetAll(ApplicationContext applicationContext) throws Exception {
        for (String name : applicationContext.getBeanDefinitionNames()) {
            Object bean = applicationContext.getBean(name);
            if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
                bean = ((Advised)bean).getTargetSource().getTarget();
            }
            if (Mockito.mockingDetails(bean).isMock()) {
                Mockito.reset(bean);
            }
        }
    }
}

As you see there is an iteration for all beans, check whether bean is mock or not, and reset the mock. I pay especially attention on call AopUtils.isAopProxy and ((Advised)bean).getTargetSource().getTarget(). If you bean contains an @Transactional annotation the mock of this bean always wrapped by spring into proxy object, so to reset or verify this mock you should unwrap it first. Otherwise you will get a UnfinishedVerificationException which can arise in different tests from time to time.

In my case AopUtils.isAopProxy is enough. But there are also AopUtils.isCglibProxy and AopUtils.isJdkDynamicProxy if you get troubles with proxying.

mockito is 1.10.19 spring-test is 3.2.2.RELEASE

Yet another way for resetting the mocks in the spring context.

https://github.com/Eedanna/mockito/issues/119#issuecomment-166823815

Assuming you're using Spring, you can easily implement this yourself by obtaining your ApplicationContext and then doing the following:

public static void resetMocks(ApplicationContext context) {
    for ( String name : context.getBeanDefinitionNames() ) {
        Object bean = context.getBean( name );
        if (new MockUtil().isMock( bean )) {
            Mockito.reset( bean );
        }
    }
}

https://github.com/Eedanna/mockito/issues/119

coupling the above code with @AfterAll should be a good way to clean up/reset the mocks.

You can indeed use @MockBean (as previously answered). It does reset the mocks after each test within that context BUT it may also re-build the entire spring context for other test classes that do not use the same exact combination of @MockBean/@SpyBean, which may lead to slow build test phases as many contexts need to start up!

If you're using spring boot 2.2+, you can use @MockInBean as an alternative to @MockBean. It will reset your mocks AND keep your Spring context clean (and tests fast).

@SpringBootTest
public class MyServiceTest {

    @MockInBean(MyService.class)
    private ServiceToMock serviceToMock;

    @Autowired
    private MyService myService;

    @Test
    public void test() {
        Mockito.when(serviceToMock.returnSomething()).thenReturn(new Object());
        myService.doSomething();
    }
}

disclaimer: I created this library for this purpose: clean the mocks and avoid Spring context constant re-creation in tests.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top