65

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 appreciated.

@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(...);

}

6 Answers 6

74
  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.

  1. Not a fan of Spring based tests

  2. 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.

  3. 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.

  4. 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 context. But could you be looking for something like :

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}
6
  • 1
    Thank you for the update. In general, I agree with you. But limited use of spring tests is still useful in some situations especially when you want to test your infrastructure and they're seperated from other parts of the system. But I do have a hestation of using spring tests for mvc controllers(I could use standalone mvc instead). Mocks are needed if I do this, but if I don't, configurations is not covered and they play a major role in mvc. Commented Aug 13, 2013 at 3:12
  • Hey I didn't say I never wrote some tests with Spring ;) If you want to seriously harness a Spring Batch I'd even recommend it, but proper data should be used! Now as you mentioned you look for infrastructure tests, and that is a different story. I'm not sure there is a lot of good or bad practice with mocks in these tests. May be that these tests should prove some ilities of the system. In my opinion mocks lifecycle is a detail there as long as it's correct and understandable. Also though they should only help for isolation.
    – bric3
    Commented Aug 13, 2013 at 10:02
  • Sorry for late answer acception. I tried springockito this weekend, but it seems to failed to load WebApplicationContext. So for me, use reset(mock) is the most convenient solution(I've refectored reseting to an @ After method when more mocks was needed). Thank you. Commented Aug 18, 2013 at 2:52
  • You're welcome. Also eventually as time goes by in the project, good practice will emerge, some specific to the project, some more general. For example I usually use multiple @After / @Before methods for dedicated tasks (named accordingly) ; and if order is required, then only one setup / teardown method than call multiple methods. Cheers.
    – bric3
    Commented Aug 18, 2013 at 12:20
  • I'm not agreed about 1) such as Infinitest are great for that), but tests with Spring are inherently more slow and more memory consuming just try gradle: gradle -t test it will re-build/run only needed classes/tests 2) @DirtiesContext - use this only if it needed Commented May 13, 2017 at 20:53
28

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;
3
  • 1
    This is the best option for mocking objects in spring test. I hardly can remember an example where mockBean doesn't suit the test. Therefore no more worries about resetting mocks
    – voipp
    Commented Nov 29, 2018 at 6:21
  • 3
    Using @MockBean may lead to slow build test phases as it may re-create the entire Spring context that it dirtied. See stackoverflow.com/questions/45587213/… or the problem with @MockBean. You can use @MockInBean as an alternative to @MockBean that does not reset the context. See my answer. Commented Mar 18, 2021 at 0:11
  • @AntoineMeyer In my personal opinion this is only true, when the developers do not separate their unit and integration tests. Unit tests, where mocks are used, there is no full spring context. The test subject is embedded into a purely mocked environment. Our integration tests do not use mocks at all. Therefore they start up the whole spring context and also run against real test databases. They are fully integrated tests. In most projects we have around 2k unit tests and 500 integration tests each. Running 2k unit tests is about 10 seconds. Running 500 integration tests around 15 minutes.
    – Mario Eis
    Commented Dec 6, 2021 at 13:40
6

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.

2
  • Thanks for the response. But this code leads to a NoSuchBeanException:No bean named 'placeOrderService' is defined. How can I inject placeOrderservice to the Controller? Commented Aug 11, 2013 at 11:37
  • What exactly are you trying to test? If you're just testing controller logic, then I would think you'd want to instantiate it yourself and mock all its dependencies, which doesn't require an ApplicationContext. Otherwise, if you're testing integration of components then you wouldn't necessarily need mocks. But if you want to use mocks in an integration test, then just overwrite the service injected in the controller by Spring with a @Mock.
    – superEb
    Commented Aug 11, 2013 at 14:07
6

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

6

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.

5

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.