Geoffrey De Vylder
Geoffrey De Vylder

Reputation: 4153

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?

Upvotes: 20

Views: 22625

Answers (5)

gpeche
gpeche

Reputation: 22514

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.

Upvotes: 41

Eugen
Eugen

Reputation: 8783

To clearly separate Unit and Integration tests (skipping over the debate of what each category means) - you can test your service in two ways:

  • via an Integration test - you fire up the entire Spring Context and test the service as a singleton bean.
  • via a Unit test - you simply initialize the service yourself, mock what needs to be mocked, with no need for Spring.

My suggestion is not to mix Spring and mocks if you can help it - keep Mockito for unit tests (which is what you need by the looks of it) and use Integration tests that bootstrap the entire Spring context for testing other things - persistence concerns, transactions, etc.

You don't need Spring to mock the collaborators of a class and do simple interaction testing with Mockito.

Upvotes: 13

quaylar
quaylar

Reputation: 2635

I have never used Mockito, but Spring-Beans are Singletons by default - so they would not be recreated unless you call refresh() on the Spring-Container.

If you anyways dont need them to be Singletons, you could set their scope to prototype which would create new bean-instances on every injection...

Upvotes: 0

Nikem
Nikem

Reputation: 5784

You may try calling setDirty(true) in your test method to reload Spring context.

Upvotes: 0

driangle
driangle

Reputation: 11779

in your @Before method, be sure to reset your mock objects.

@Before
public void setup(){
    Mockito.reset(validator);
}

Upvotes: 3

Related Questions