Woods
Woods

Reputation: 149

How to inject spring context inside inner test class? Or what else workarounds exists?

I have a test class which should be structured with inner/nested classes inside it. A guy recommended me to use @RunWith(HierarchicalContextRunner.class) to run it. From what runners I've already tried I like this more, and I'd like to keep using it. I've built my class based on this advice. It works nicely in outer class, all tests inside it work fine. But everything inside inner class throws this:

IllegalStateException: Failed to load ApplicationContext
...
Caused by: java.lang.IllegalStateException: Neither GenericXmlContextLoader nor AnnotationConfigContextLoader was able to load an ApplicationContext from [MergedContextConfiguration@4aa22cc2 testClass = AbstractBaseEntityGenericDao_Test.Find_Method, locations = '{}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]. 

MyTestClass:

    @ActiveProfiles(profiles = "test") 
    @RunWith(HierarchicalContextRunner.class) 
    @ContextConfiguration(classes = {
            PersistenceConfig.class,
            RootConfig.class }) 
    @WebAppConfiguration 
    @Transactional 
    @Rollback 

    public class AbstractBaseEntityGenericDao_Test {

        private static final String[] CUSTOMER_NAMES = {"Veronica", "Hanna", "Eric"};

        @ClassRule
        public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

        @Rule
        public final SpringMethodRule springMethodRule = new SpringMethodRule();

        @Autowired
        private ApplicationContext applicationContext;

        @Autowired
        private SessionFactory sessionFactory;

        @Rule
        public ExpectedException thrown = ExpectedException.none();

        private AbstractClassStub abstractClassStub;

        private class AbstractClassStub extends AbstractBaseEntityGenericDao<NamedStubEntity> {

            public AbstractClassStub(SessionFactory sessionFactory) {
                setClassInstance(NamedStubEntity.class);
                this.sessionFactory = sessionFactory;
            }

            @Override
            public void create(NamedStubEntity entity) {
                super.create(entity);
            }

            @Override
            public Optional find(Long id) {
                return super.find(id);
            }

            @Override
            public void update(NamedStubEntity entity) {
                super.update(entity);
            }

            @Override
            public void remove(@NonNull Long id) throws EntityNotFoundException {
                super.remove(id);
            }

            @Override
            public void remove(NamedStubEntity entity) {
                super.remove(entity);
            }
        }

        @Before
        public void setUp() throws Exception {
            abstractClassStub = new AbstractClassStub(sessionFactory);

            DbTestUtil.resetAutoIncrementColumns(applicationContext, "base_stub_entity");

            //...
        }

        @After
        public void tearDown() throws Exception {
            //...
        }

        @Test
        public void find_outer_test() {
            //Successful
        }


        public class Find_Method {

            @Test
            public void find_must_return_entity_in_database_by_id() {
                //Unsuccessful
            }

            @Test
            public void find_must_throw_NullPointerException() {
                //Unsuccessful
            }

            @Test
            public void find_should_return_BaseStubEntity() {
                //Unsuccessful
            }

        } }

Well... my question is - how to run everything with spring-context, not only the outer class?

UPD: Modified classes:

AbstractSpringTest

@ContextConfiguration(classes = {
        PersistenceConfig.class,
        RootConfig.class
})
@WebAppConfiguration
@Transactional
@Rollback
public class AbstractSpringTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    protected SessionFactory sessionFactory;
}

AbstractBaseEntityGenericDao_Test

@ActiveProfiles(profiles = "test")
@RunWith(HierarchicalContextRunner.class)
public class AbstractBaseEntityGenericDao_Test extends AbstractSpringTest {

    private AbstractClassStub abstractClassStub;

    private class AbstractClassStub extends AbstractBaseEntityGenericDao<NamedStubEntity> {

        public AbstractClassStub(SessionFactory sessionFactory) {
            setClassInstance(NamedStubEntity.class);
            this.sessionFactory = sessionFactory;
        }

        @Override
        public void create(NamedStubEntity entity) {
            super.create(entity);
        }

        //...
    }

    @Before
    public void setUp() throws Exception {
        abstractClassStub = new AbstractClassStub(sessionFactory);

        DbTestUtil.resetAutoIncrementColumns(applicationContext, "base_stub_entity");

        //...
    }

    @After
    public void tearDown() throws Exception {
        //...
    }

    @Test
    public void find_must_find() {
        //Successful
    }

    public class Find_Method extends AbstractSpringTest {

        @Test
        public void find_must_throw_NullPointerException() {
            //Unsuccessful
        }

        @Test
        public void find_should_return_BaseStubEntity() {
            //Unsuccessful
        }

    }
}

How it looks in the debugger. And the debugger doesn't even go into the inner methods. It stops on class level I've tried to make the inner class as static to make it visible outside, but HierarchicalContextRunner stops seeing the test methods inside a nested class.

Upvotes: 0

Views: 1205

Answers (1)

Filippo Possenti
Filippo Possenti

Reputation: 1410

You're very close. In fact, your approach may even be correct with the problem being related to configuration files rather than the test itself.

Broadly speaking, the simplest way is to put the related annotations also on the nested classes, which I assume is what you want to avoid.

A way to make it cleaner is to have your test classes all inherit from a common class. The class annotated will then be the common class. This works particularly well for me as I don't even have to write all the various Spring annotations in each and every test. This will require you to use the @ClassRule and @Rule annotations in conjunction with SpringClassRule and SpringMethodRule and @RunWith(Enclosed.class) or similar... like you are already doing.

In your case, the Find_Method class would have to inherit from an AbstractSpringTest class which in turn would be annotated with your @WebAppConfiguration and associated annotations. Note however that the Find_Method class should be declared as static.

Here follows a partial extract of how such setup would look like:

package mypackage;

import org.junit.Rule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;

import org.junit.ClassRule;
import org.springframework.test.context.junit4.rules.SpringClassRule;

import ...

@WebAppConfiguration
@ContextConfiguration(classes = { TestConfigurationClass1.class, TestConfigurationClass2.class })
public class AbstractSpringTest {

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
}

And here's how your test classes would look like:

@RunWith(Enclosed.class)
public class MyTestClass extends AbstractSpringTest {

  ...
  public static class MyNestedTestClass extends AbstractSpringTest {
    ...
  }

  public static class MyOtherNestedTestClass extends AbstractSpringTest {
    ...
  }
}

Needless to say, using "rules" allows you to have whatever @RunWith annotation you see fit for your purpose. In my case I find very useful the possibility to use this approach to run @Parameterized tests.

The error you described, however, seems to point to problems in loading the associated configuration files (application.properties, application-context.xml and similar). The general advice is to double-check that all the configuration files are available on the right path of the classpath for use by the tests. Note that this means for example checking the contents of the target directory as well as excluding that the IDE is having a "temper tantrum" and refusing manage specific resources appropriately, the best way being to run a mvn clean install.

Upvotes: 1

Related Questions