0

I am currently playing around with springockito-annotations and this requires @RunWith and @ContextConfiguration annotations in order to work. I would like to put these annotations on a superclass of my tests, but can't seem to get it to work.

Superclass:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
@ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:spring/test-context.xml")
public class MyTestSuperclass {
    //...

Example testclass:

public class MyTest extends MyTestSuperclass {
    @Inject
    @ReplaceWithMock
    private MyService myService;
    //...

With this code, myService is not replaced with a mock.

But, if I change it to...

@ContextConfiguration
public class MyTest extends MyTestSuperclass {
    //...

...it works.

Is there a way to avoid having to add @ContextConfiguration to all my test classes? Has this perhaps been fixed in a newer version of Spring/Spring-tests? From what I can tell it is able to inherit the locations part from the superclass without annotating the subclass, but the loader part does not work without an annotation in the subclass. I'm using version 3.2.1.RELEASE of Spring-test.

Here is a sample projec that shows the bug:

http://www.filedropper.com/springockitobug

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
Tobb
  • 11,850
  • 6
  • 52
  • 77
  • `spring-test` has supported inheritance of loaders since Spring Framework 3.0. So you should definitely not need to redeclare an empty `@ContextConfiguration`. There may be an issue with the Springockito implementation. Would you be willing to publish a minimal example project to demonstrate this behavior (e.g., on GitHub)? – Sam Brannen Jan 28 '16 at 15:45
  • I think the culprit is `springockito-annotations`, I also had issues when the annotated class had an un-annotated superclass, in these cases `@ReplaceWithMock` didn't work as well.. – Tobb Jan 29 '16 at 08:57
  • Added a minimal project. Couldn't reproduce the error in the above comment in the minimal project so must have been due to something else.. – Tobb Jan 29 '16 at 09:31
  • What do you mean you couldn't reproduce the error? I downloaded your sample project, and it definitely reproduced the original error. Your sample project is what I used to verify that `PatchedSpringockitoContextLoader` works. So... thanks for providing the sample project! – Sam Brannen Jan 29 '16 at 18:29
  • Was referring to the comment above. – Tobb Jan 29 '16 at 20:13

2 Answers2

2

This is due to a bug in Springockito.

@ContextConfiguration is in fact inherited, which has been true since it was introduced in Spring 2.5. Furthermore, the configured loader (i.e., SpringockitoContextLoader in this case) is also inherited, which has been true since Spring 3.0.

The issue here is that SpringockitoContextLoader incorrectly processes the declaring class (i.e., the class that is annotated with @ContextConfiguration) instead of the actual test class (which can be a subclass that inherits the declaration of @ContextConfiguration).

After thorough debugging, it turns out that the solution is quite simple (in fact simpler than the original SpringockitoContextLoader implementation).

The following PatchedSpringockitoContextLoader should work fine as a drop-in replacement for the broken SpringockitoContextLoader.

import org.kubek2k.springockito.annotations.internal.Loader;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.GenericXmlContextLoader;

public class PatchedSpringockitoContextLoader extends GenericXmlContextLoader {

    private final Loader loader = new Loader();

    @Override
    protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
        super.prepareContext(context, mergedConfig);
        this.loader.defineMocksAndSpies(mergedConfig.getTestClass());
    }

    @Override
    protected void customizeContext(GenericApplicationContext context) {
        super.customizeContext(context);
        this.loader.registerMocksAndSpies(context);
    }

}

Regards,

Sam (author of the Spring TestContext Framework)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
0

Instead of using a super class, use an annotation.

For instance:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
@ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:spring/test-context.xml")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestConfiguration {
}

Then, annotate your test classes with

@TestConfiguration 
public class MyTest  {
    @Inject
    @ReplaceWithMock
    private MyService myService;
    //...

The IDE's syntax highlighting may complain about not being allowed to inject, depending which you use, but my initial tests have this working

Farrell
  • 508
  • 3
  • 11
  • I'm trying to avoid annotation each and every test class, changing `@ContextConfiguration` to `@TestConfiguration` doesn't really help that (unless I can put `@TestConfiguration` on a superclass and avoid it on subclasses.) But didn't know this was possible, good to know :) – Tobb Jan 26 '16 at 14:34
  • I don't have a class framework set up to confirm, but you may be able to put `@Inherited` onto the `@TestConfiguration`'s definition. Then put that on your base class - if that works, just say and I can update the answer. – Farrell Jan 26 '16 at 14:41
  • 1
    FYI: `@RunWith` can _not_ be used as a meta-annotation. That's simply not supported by JUnit. Thus, I'm not sure what you are claiming works. With your example, the standard `JUnit4` runner would be used, not the `SpringJUnit4ClassRunner`. – Sam Brannen Jan 30 '16 at 17:59
  • I only carried out a very quick proof of concept test, which appeared to work, before I posted my solution. [Running on Spring 4.1.3, Junit 4.11]. I used a few autowired dependencies, and they were referenced and populated OK. However, examining the set up I had shows that I have an inherited @RunWith down my test suite hierarchy using the expected Spring class [I had only made minor modifications to a project I'm working on], so it gave a false positive. – Farrell Feb 01 '16 at 11:21