Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to clean up mocks in spring tests when using Mockito

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(...);  } 
like image 464
Yugang Zhou Avatar asked Aug 10 '13 16:08

Yugang Zhou


People also ask

How do you reset mock Before each test jest?

afterEach(() => { jest. clearAllMocks(); }); to call jest. clearAllMocks to clear all mocks after each test.

What is the difference between @mock and @MockBean?

We can use the @MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context. If no bean of the same type is defined, a new one will be added.


2 Answers

  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); } 
like image 70
Brice Avatar answered Sep 23 '22 04:09

Brice


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; 
like image 39
Lu55 Avatar answered Sep 24 '22 04:09

Lu55