【问题标题】:Spring bean scope for "one object per test method"“每个测试方法一个对象”的 Spring bean 范围
【发布时间】:2019-10-13 23:31:00
【问题描述】:

我有一个测试实用程序,我需要每个测试方法都有一个新实例(以防止测试之间的状态泄漏)。到目前为止,我使用的范围是“原型”,但现在我希望能够将该实用程序连接到另一个测试实用程序,并且每次测试的连接实例都应相同。

这似乎是一个标准问题,所以我想知道是否有“测试方法”范围或类似的东西?

这是测试类和测试实用程序的结构:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {

    @Autowired
    private TestDriver driver;

    @Autowired
    private TestStateProvider state;

    // ... state
    // ... methods
}
@Component
@Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
    // ...
}
@Component
public class TestStateProvider {

    @Autowired
    private TestDriver driver;

    // ...
}

我知道我可以使用 @Scope("singleton")@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) 但这比我需要的刷新更多 - 每个测试一个新的 TestDriver 实例就足够了。此外,这种方法容易出错,因为所有使用TestDriver 的测试都需要知道它们还需要@DirtiesContext 注释。所以我正在寻找更好的解决方案。

【问题讨论】:

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


    【解决方案1】:

    实际上实现testMethod 范围很容易:

    public class TestMethodScope implements Scope {
        public static final String NAME = "testMethod";
    
        private Map<String, Object> scopedObjects = new HashMap<>();
        private Map<String, Runnable> destructionCallbacks = new HashMap<>();
    
        @Override
        public Object get(String name, ObjectFactory<?> objectFactory) {
            if (!scopedObjects.containsKey(name)) {
                scopedObjects.put(name, objectFactory.getObject());
            }
            return scopedObjects.get(name);
        }
    
        @Override
        public void registerDestructionCallback(String name, Runnable callback) {
            destructionCallbacks.put(name, callback);
        }
    
        @Override
        public Object remove(String name) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public String getConversationId() {
            return null;
        }
    
        @Override
        public Object resolveContextualObject(String key) {
            return null;
        }
    
        public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
    
            @Override
            public void afterTestMethod(TestContext testContext) throws Exception {
                ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
                        .getApplicationContext();
                TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
    
                scope.destructionCallbacks.values().forEach(callback -> callback.run());
    
                scope.destructionCallbacks.clear();
                scope.scopedObjects.clear();
            }
        }
    
        @Component
        public static class ScopeRegistration implements BeanFactoryPostProcessor {
    
            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
                factory.registerScope(NAME, new TestMethodScope());
            }
        }
    
    }
    

    只需注册测试执行监听器,所有@Scope("testMethod")注解类型的每个测试都会有一个实例:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class, 
            mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
    public class MyTest {
    
        @Autowired
        // ... types annotated with @Scope("testMethod")
    
    }
    

    【讨论】:

      【解决方案2】:

      我前段时间遇到了同样的问题并得出了这个解决方案:

      1. 使用模拟
      2. 我编写了一些方法来创建特定的 mockito 设置来为每个 mock 添加行为。

      因此,使用以下方法和 bean 定义创建一个 TestConfiguration 类。

          private MockSettings createResetAfterMockSettings() {
              return MockReset.withSettings(MockReset.AFTER);
          }
      
          private <T> T mockClass(Class<T> classToMock) {
              return mock(classToMock, createResetAfterMockSettings());
          }
      

      您的 bean 定义将如下所示:

      @Bean
      public TestDriver testDriver() {
          return mockClass(TestDriver .class);
      }
      

      MockReset.AFTER用于在测试方法运行后重置mock。

      最后将TestExecutionListeners 添加到您的测试类中:

      @TestExecutionListeners({ResetMocksTestExecutionListener.class})
      

      【讨论】:

      • 如果我想重置模拟的状态(例如方法调用计数器),我可以看到这将如何工作,但TestDriver 是一个真正的实现类,我想重置状态该实施。这行不通,不是吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-26
      • 1970-01-01
      相关资源
      最近更新 更多