2

I would like some of my beans know something about test. SOMETHING. May be test class name or some of it's methods.

For example, suppose my test class has a method

public String getTestName() {
   return getClass().getSimpleName();
}

This method returns test name and can be overridden.

Is it possible to inject this name into some beans of Spring context, to use during test?

For example, with autowire feature:

@Autowired
public String testName;

not only in test class, but in other beans too.

UPDATE

Below are two (failed) attempts to implement injecting testInstance. May be there are some convenient ways to do that?

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestClassAwareTry._Config.class)
@TestExecutionListeners(value = { TestClassAwareTry._Listener.class },
   mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)

public class TestClassAwareTry {

   /**
    * Interface to tag beans, who want to know if they are in test
    */
   public interface TestInstanceAware {
      void setTestInstance(Object value);
   }

   /**
    * Sample bean, which would like to know if it is in test
    */
   public static class MyBean implements TestInstanceAware {

      private Object testInstance;

      {
         System.out.println("MyBean constructed");
      }

      public void setTestInstance(Object value) {
         this.testInstance = value;
         System.out.println("testInstance set");
      }

      public Object getTestInstance() {
         return testInstance;
      }
   }

   /**
    * Attempt to inject testInstance with a bean, implementing {@link BeanPostProcessor}
    */
   public static class TestInstanceInjector implements BeanPostProcessor {


      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         if( bean instanceof TestInstanceAware ) {
            TestInstanceAware aware = (TestInstanceAware) bean;

            // we don't have access to test instance here
            // otherwise I would write
            //Object testInstance = getTestInstance();
            //aware.setTestInstance(testInstance);
         }
         return bean;
      }

      public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
         return bean;
      }
   }

   /**
    * Attempt to inject testInstance with test execution listener
    */
   public static class _Listener extends AbstractTestExecutionListener {

      @Override
      public void prepareTestInstance(TestContext testContext) throws Exception {
         Object testInstance = testContext.getTestInstance();
         ApplicationContext context = testContext.getApplicationContext();

         // we don't have setBean() method
         // I would write if I have
         // context.setBean("testInstance", context);

      }

   }

   /**
    * Java-based configuration
    */
   @Configuration
   public class _Config {

      @Bean
      public MyBean myBean() {
         return new MyBean();
      }

      @Bean
      public TestInstanceInjector testInstanceInjector() {
         return new TestInstanceInjector();
         // I would acquire test instance here and pass it to constructor, if I can
      }

   }

   @Autowired
   public MyBean myBean;

   @Test
   public void testInjected() {
      assertSame( this, myBean.getTestInstance());
   }
}
8
  • for the second part: look at Spring's Integration Test support: docs.spring.io/spring/docs/current/spring-framework-reference/… Commented Oct 18, 2015 at 18:54
  • Looking for a long time, but can't find. Just question matured :) Commented Oct 18, 2015 at 18:59
  • Why do you need this? Commented Oct 19, 2015 at 10:44
  • I need my beans write some information into test-specific directory when testing. So, I need to setup my beans with some information, depending on test. Commented Oct 19, 2015 at 10:46
  • Do they only write this data during test, or do they write this data even in live? Commented Oct 19, 2015 at 14:12

4 Answers 4

2

I've ended up creating ContextCustomizerFactory that registers BeanPostProcessor

package com.company.testing.base.spring;

import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;

public class TestAwareContextCustomizerFactory implements ContextCustomizerFactory {

  @Override
  public ContextCustomizer createContextCustomizer(
      Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
    return (context, mergedConfig) -> {
      ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
      beanFactory.addBeanPostProcessor(
          new TestInstanceAwareBeanPostProcessor(mergedConfig.getTestClass()));
    };
  }
}

TestInstanceAwareBeanPostProcessor

public class TestInstanceAwareBeanPostProcessor implements BeanPostProcessor {

  private final Class<?> testClass;

  TestInstanceAwareBeanPostProcessor(Class<?> testClass) {
    this.testClass = testClass;
  }

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    if (bean instanceof TestClassAware) {
      ((TestClassAware) bean).setTestClass(testClass);
    }
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }
}

resources/META-INF/spring.factories

# ContextCustomizerFactories for the Spring TestContext Framework
org.springframework.test.context.ContextCustomizerFactory = \
  com.company.testing.base.spring.TestAwareContextCustomizerFactory
Sign up to request clarification or add additional context in comments.

1 Comment

This approach looks interesting, but there are a couple of missing pieces. Could you share an example of the TestClassAware class as well as how one would go about consuming the TestAwareContextCustomizerFactory class to inject test information?
1

The only way I've been able to do this is by delaying creation of the subject until you are in the test method and to have the bean in the prototype scope.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { LiveConfig.class, DevConfig.class})
@ActiveProfiles("Dev")
public class MeTest {
    @Autowired
    public ApplicationContext context;

    @Autowired
    DevConfig devConfig;

    @Rule
    public TestName nameRule = new TestName();

    @Before
    public void setName() {
        devConfig.setSettings(nameRule.getMethodName());
    }

    @Test
    public void test() {
        Bean subject = context.getBean(Bean.class);
        System.out.println(subject.settings);
        assertThat(subject.settings, is(nameRule.getMethodName()));
    }

    @Test
    public void test2() {
        Bean subject = context.getBean(Bean.class);
        System.out.println(subject.settings);
        assertThat(subject.settings, is(nameRule.getMethodName()));
    }
}

@Configuration
class LiveConfig {
    @org.springframework.context.annotation.Bean
    public String getSettings() {
        return "/some/real/file.txt";
    }

    @org.springframework.context.annotation.Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Bean getBean() {
        return new Bean();
    }
}

@Configuration
class DevConfig {
    private String settings;

    @org.springframework.context.annotation.Bean
    @Profile("Dev")
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public String getSettings() {
        return settings;
    }

    public void setSettings(String settings) {
        this.settings = settings;
    }
}

class Bean {
    public Bean() {
        System.out.println("Bean");
    }


    String settings;
    @Autowired
    void setSettings(String settings) {
        System.out.println("Settings: " + settings);
        this.settings = settings;
    }
}

This uses Profiles to change what Live sees and what the tests see, and the a NameRule to get the name. It is clunky.

I would NOT use the TestName rule, but rather the TemporaryFolder rule and use that to set whatever setting your application uses for the output folder. I'd also only use DI in a test in very rare cases (i.e. full blown integration tests).

Comments

0

Do you mean like this?

public class MyTest {

  @Test
  public void testName() {
    MyBean b = new MyBean(MyTest.class.getSimpleName());
    b.doSomething();
  }

}

Comments

0

You can achieve this in a more elegant way using Spring Boot Auto configuration feature by making yours, this way:

  1. define a Configuration class that exposes or registers your bean this way:

    
    @Configuration
    public class MyBeanProviderConfiguration {
       @ConditionalOnMissingBean
       @Bean
       public MyBean myBean() {
          // return a fully initialised MyBean instance
       }
    }
    
  2. Then define a custom annotation Spring Boot like, say @AutoConfigureMyBean this way:

    
    @Retention(RetentionPolicy.RUNTIME) 
    @Target(ElementType.TYPE) 
    @ImportAutoConfiguration(MyBeanProviderConfiguration.class) 
    public @interface AutoConfigureMyBean {}   
    
  3. Then you can use this in your Spring test, here is an example:

    
    @RunWith(SpringRunner.class)
    @AutoConfigureMyBean
    public class MyTest {
       @Autowired
       MyBean myBean;
    }
    

Or also declare your MyBean @Autowired dependent bean in a regular Spring test (using a Config class), A MyBean instance will be automatically injected into it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.