Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a new instance of a bean after each unit test

I'm new to the spring framework and have a question about its dependency injection capabilities using the Spring Context.

This is the class I am trying to write an integration test for:

public class UserService {

private Validator validator;
private UserRepository userRepository;
private Encryptor encryptor;
private MailService mailService;

...

public void registerUser(User user) {
    user.setPassword(encryptor.encrypt(user.getPassword()));

    Errors errors = new BindException(user, "user");
    validator.validate(user, errors);

    if (errors.getErrorCount() == 0) {
        userRepository.addUser(user);
        mailService.sendMail(user.getEmail());
    }
}

In my tests (using Mockito) I want to assure the four items are called so I create tests like:

public void testRegisterCallsValidateInValidator() {
    userService.registerUser(testUser);
    verify(userService.getValidator(), times(1)).validate(any(User.class), any(Errors.class));
}

All tests however fail saying I invoked the method multiple times. My only guess is that the UserService bean gets created once at the beginning of all the tests but doesn't get reloaded after each test.

In my test configuration I use the following xml to decide which beans to inject:

<bean id="userService" class="be.kdg.coportio.services.UserService">
    <property name="validator" ref="validator"/>
    <property name="userRepository" ref="userRepository"/>
    <property name="encryptor" ref="encryptor"/>
    <property name="mailService" ref="mailService"/>
</bean>

Any ideas?

like image 770
Geoffrey De Vylder Avatar asked Feb 10 '12 07:02

Geoffrey De Vylder


1 Answers

You are reusing your context, in order to have tests independent from one another you probably need to refresh your context after each test to reset everything.

I will suppose you are using Junit 4.5+. It would be similar with other test frameworks.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"mycontext.xml"})
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyTestClass {
...
    // my tests    
...
}

You can put the @DirtiesContext at the method level if the methods that need "fixing" are few, but if you are using Spring your best option is to do it after every test.

Anyway, I don't think you should be using mocks/spies in integration tests:

  • In unit tests, use mocks (if you want) and inject manually. Here you want to verify the behaviour of your tested bean as a unit, so you isolate it from the rest by using mocks. This also has the advantage that JUnit isolates your tests by using a different instance of the test class for each test, so unless you use static or other test-unfriendly practices everything will just work.

  • In integration tests, use real beans and let Spring inject. Here the goal is to verify that beans interact well with one another / with the environment (database, network, ...) You do not want to isolate beans here, so you should not use mocks.

See Spring documentation about testing for more detailed explanations.

like image 175
gpeche Avatar answered Sep 23 '22 07:09

gpeche