【问题标题】:How to run Spring unit tests with a different JUnit Runner?如何使用不同的 JUnit Runner 运行 Spring 单元测试?
【发布时间】:2018-02-15 00:15:07
【问题描述】:

我正在多线程中测试一个事件处理器。所以我在我的测试用例中使用了 vmlens 的 concurrent-junit。 但是当我使用 ConcurrentTestRunner 而不是 SpringJunit4ClassRunner 自动装配 bean 时,我得到了空点异常。 这是我的pom

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.vmlens</groupId>
        <artifactId>concurrent-junit</artifactId>
        <version>1.0.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>2.6.0</version>
        <scope>test</scope>
    </dependency>

测试用例源码:

import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;

@RunWith(ConcurrentTestRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {
    @Autowired
    private EventStore es; //defined in applicationContext.xml
    @Autowired
    private EntityAppender ea;  //a @Component
......
    @Test
    @ThreadCount(10)
    public final void testDefaultListener() {
    Long bef = es.countStoredEvents();// nullpoint exception
    TestEntity1 t1 = ea.appWithDefaultListener();// nullpoint exception
    ......
    }
}

显然,bean 没有正确注入。 有没有什么办法解决这一问题?我应该扩展 AbstractJUnit4SpringContextTests 吗?


此处附上最新代码:

EventStore 是一个 Jpa 存储库:

public interface EventStore extends JpaRepository<DomainEvent, Long>{};

applicationContext.xml

<aop:config proxy-target-class="true" />
<context:annotation-config />
<jpa:repositories base-package="com.my"></jpa:repositories>

EntityAppender 只是为了测试而定义的。

@Component
public class EntityAppender {
    @Autowired
    private TestEntity1Repository myRepository; //another Jpa repository

    public EntityAppender() {
        super();
    }

    @Transactional
    public TestEntity1 appWithDefaultListener() {
        TestEntity1 t1 = new TestEntity1(UUID.randomUUID().toString().replaceAll("-", ""), "aaaaaaaaaaaa", 44,
                LocalDate.now());
        return myRepository.save(t1);
    }
...
}

测试用例:

import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;

@RunWith(ConcurrentTestRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    private EventStore es;
    @Autowired
    private EntityAppender ea;
......
    @Before
    public void setUp() throws Exception {
        bef = es.count();  //<====nullpoint exception due to es being null here
    }

    @Test
    @ThreadCount(10)
    public final void testDefaultListener() {
    bef = es.count();   //<======  es worked well here
    TestEntity1 t1 = ea.appWithDefaultListener();
    ......
    }
}

【问题讨论】:

  • 查看解决方案@ClassRule@Rule建议here,这样可以避免使用Spring的SpringJUnit4ClassRunner
  • @SotiriosDelimanolis 我也找到了相同的解决方案here,谢谢。
  • @SotiriosDelimanolis 出现了另一个问题。当我在“@Before”方法中使用自动装配时,它给了我一个空点。 Spring 上下文似乎在“@Test”方法之前注入了组件。
  • 你必须提供一个例子。 @Before 方法应该在注入发生后调用。
  • 很遗憾,我无法重现您所看到的行为。您能否在此处或在新问题中提供minimal reproducible example

标签: java multithreading junit


【解决方案1】:

从 Spring 4.2 开始,当您出于其他原因需要提供自己的 JUnit Runner(例如 ConcurrentTestRunner 或 Mockito 的 MockitoJUnitRunner)时,Spring 提供了单独的机制来初始化单元测试的 ApplicationContext 和 meta配置。

此机制是两个 JUnit 4 规则的组合。这在官方文档here中有记录。

org.springframework.test.context.junit4.rules 包提供 遵循 JUnit 4 规则(在 JUnit 4.12 或更高版本上受支持)。

  • SpringClassRule
  • SpringMethodRule

SpringClassRule 是一个支持类级别特性的 JUnit TestRule Spring TestContext 框架;而SpringMethodRule 是 JUnit MethodRule 支持实例级和方法级 Spring TestContext 框架的特性。

您的单元测试类应至少包含

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

这是一个完整的例子,它使用了MockitoJUnitRunner,同时仍然使用了 Spring 的 TestContext 框架:

@ContextConfiguration(classes = ConfigExample.class)
@RunWith(MockitoJUnitRunner.class)
public class Example {
    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    private Foo foo;

    @Mock
    private Runnable runnable;

    @Test
    public void method2() {
        Mockito.doNothing().when(runnable).run();
        foo.bar(runnable);
        Mockito.verify(runnable).run();
    }
}

@Configuration
class ConfigExample {
    @Bean
    public Foo Foo() {
        return new Foo();
    }
}

class Foo {
    public void bar(Runnable invoke) {
        invoke.run();
    }
}

【讨论】:

  • 正如我之前评论的那样,我追溯了引导过程,发现注入发生在“@Test”方法之前,但在“@Before”方法之后。我在问题中附上了上面的代码。
猜你喜欢
  • 1970-01-01
  • 2019-06-06
  • 2017-12-26
  • 2010-09-26
  • 2016-04-30
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多