为了账号安全,请及时绑定邮箱和手机立即绑定

“每个测试方法一个对象”的 Spring bean 范围

“每个测试方法一个对象”的 Spring bean 范围

明月笑刀无情 2022-12-21 13:08:19
我有一个测试实用程序,我需要每个测试方法都有一个新实例(以防止测试之间的状态泄漏)。到目前为止,我使用的是范围“原型”,但现在我希望能够将该实用程序连接到另一个测试实用程序中,并且每个测试的连接实例都应该相同。这似乎是一个标准问题,所以我想知道是否有“测试方法”范围或类似的东西?这是测试类和测试实用程序的结构:@RunWith(SpringRunner.class)@SpringBootTestpublic class MyTest {    @Autowired    private TestDriver driver;    @Autowired    private TestStateProvider state;    // ... state    // ... methods}@Component@Scope("prototype") // not right because MyTest and TestStateProvider get separate instancespublic class TestDriver {    // ...}@Componentpublic class TestStateProvider {    @Autowired    private TestDriver driver;    // ...}我知道我可以使用@Scope("singleton"),@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)但这比我需要的刷新更多——TestDriver每个测试一个新实例就足够了。此外,这种方法容易出错,因为所有使用 的测试TestDriver都需要知道它们也需要@DirtiesContext注释。所以我正在寻找更好的解决方案。
查看完整描述

2 回答

?
四季花海

TA贡献1811条经验 获得超5个赞

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")


}


查看完整回答
反对 回复 2022-12-21
?
慕容森

TA贡献1853条经验 获得超18个赞

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

  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用于在运行测试方法后重置模拟。


最后添加一个TestExecutionListeners到你的测试类:


@TestExecutionListeners({ResetMocksTestExecutionListener.class})


查看完整回答
反对 回复 2022-12-21
  • 2 回答
  • 0 关注
  • 129 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信