如何在弹簧测试中注入有关测试的任何信息?

时间:2015-10-18 18:44:21

标签: java spring dependency-injection junit4 spring-test

我想我的一些豆子知道一些关于测试的东西。一些东西。可能是测试类名称或其中的一些方法。

例如,假设我的测试类有一个方法

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

此方法返回测试名称,可以覆盖。

是否可以将此名称注入Spring上下文的某些bean中,以便在测试期间使用?

例如,使用自动装配功能:

@Autowired
public String testName;

不仅在测试类中,而且在其他bean中也是如此。

更新

以下是两次(失败的)尝试实施注入testInstance。可能有一些方便的方法吗?

@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());
   }
}

4 个答案:

答案 0 :(得分:1)

我能够做到这一点的唯一方法是延迟创建主题,直到你进入测试方法并将bean放在原型范围内。

@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;
    }
}

这使用Profile来更改Live看到的内容和测试的内容,并使用NameRule来获取名称。它很笨重。

我不会使用TestName规则,而是使用TemporaryFolder规则,并使用它来设置应用程序用于输出文件夹的任何设置。在非常罕见的情况下(即全面集成测试),我也只在测试中使用DI。

答案 1 :(得分:1)

我最终创建了注册BeanPostProcessor的ContextCustomizerFactory

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

答案 2 :(得分:0)

你的意思是这样吗?

public class MyTest {

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

}

答案 3 :(得分:0)

您可以使用Spring Boot的自动配置功能,以更优雅的方式实现此目的,方法是:

  1. 定义一个Configuration类,该类以这种方式公开或注册您的bean:

    
    @Configuration
    public class MyBeanProviderConfiguration {
       @ConditionalOnMissingBean
       @Bean
       public MyBean myBean() {
          // return a fully initialised MyBean instance
       }
    }
    
  2. 然后定义一个自定义注释 Spring Boot之类的,用这种方式说@AutoConfigureMyBean

    
    @Retention(RetentionPolicy.RUNTIME) 
    @Target(ElementType.TYPE) 
    @ImportAutoConfiguration(MyBeanProviderConfiguration.class) 
    public @interface AutoConfigureMyBean {}   
    
  3. 然后您可以在春季测试中使用它,下面是一个示例:

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

或者在常规的Spring测试中(使用Config类)声明与您的 MyBean @Autowired相关的bean,MyBean实例将自动注入到其中。