Spock 测试失败,RetrySynchronizationManager.getContext() 为 null

问题描述 投票:0回答:1

我编写了一个 Spock 测试来使用 Spring-retry 来执行服务,但测试上下文未以某种方式正确设置。

具体错误是(@kriegaex插入的换行符):

[ERROR]
  IntegrationTestSpec.test success the first time:25 » 
  NullPointer Cannot invoke "org.springframework.retry.RetryContext.getRetryCount()"
  because the return value of
  "org.springframework.retry.support.RetrySynchronizationManager.getContext()"
  is null

有一个上一个问题询问相同的错误,但没有提供重现该问题的代码,因此它没有走得太远。这个问题的完整代码如下,也位于 https://github.com/nathan-hughes/spring-retry-examples

失败的测试:

package ndhtest

import spock.lang.Specification
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.beans.factory.annotation.Autowired
import org.spockframework.spring.SpringBean
import spock.lang.Subject

@SpringBootTest(classes = [MyRetryableService, RandomNumberService])
class IntegrationTestSpec extends Specification {

    @SpringBean
    RandomNumberService randomNumberService = Stub(RandomNumberService)

    @Subject
    @Autowired
    MyRetryableService myRetryableService

    def "test success the first time"() {
        given:
        randomNumberService.randomNumber() >> 1

        when:
        int result = myRetryableService.doStuff('hello')

        then:
        result == 1
    }
}

要测试的代码:

package ndhtest;

import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
@Slf4j
@RequiredArgsConstructor
public class SpringBootConsoleApplication implements CommandLineRunner {

    private final MyRetryableService myRetryableService;

    public static void main(String ... args) throws Exception {
        log.info("starting application");
        SpringApplication.run( SpringBootConsoleApplication.class);
        log.info("finishing application");
    }

    @Override
    public void run(String ... args) {
        log.info("executing command line runner");
        try {
            int i = myRetryableService.doStuff("hello");
            log.info("myRetryableService returned {}", i);
        } catch (Exception e) {
            log.error("caught exception", e);
        }
    }
}

执行重试的服务,它具有获取重试计数的日志记录,这在 Spock 测试之外运行代码时有效:

package ndhtest;

import java.util.Random;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.support.RetrySynchronizationManager;
import org.springframework.stereotype.Service;

@Service
@Slf4j
@RequiredArgsConstructor
public class MyRetryableService {

    private final RandomNumberService randomNumberService;

    @Retryable(value = {RetryableException.class}, maxAttempts = 2, backoff = @Backoff(delay=100))
    public int doStuff(String s) {

        log.info("doing stuff with {}, try = {}", s, RetrySynchronizationManager.getContext().getRetryCount() + 1);
        int i = randomNumberService.randomNumber();

        // simulate having something bad happen that is recoverable
        if (i % 2 == 0) {
            String issue = "oops";
            log.warn("condition = {}", issue);
                throw new RetryableException(issue);
            }

        // simulate having something bad happen that is not recoverable
        if (i % 5 == 0) {
            String issue = "ohnoes";
            log.warn("condition = {}", issue);
            throw new IllegalArgumentException(issue);
        }

        return i;
    }

    @Recover
    public int recover(RetryableException e, String s) {

        log.info("in recover for RetryableException, s is {}", s);
        return -1;
    }

    @Recover
    public int recover(RuntimeException e, String s) {

        log.info("in recover for RuntimeException, s is {}", s);
        throw e;
    }
}

配角:

package ndhtest;

import java.util.Random; 
import org.springframework.stereotype.Service;

@Service
public class RandomNumberService {

    private Random random = new Random();

    public int randomNumber() {
        return random.nextInt();
    }
}
package ndhtest;

public class RetryableException extends RuntimeException {

    public RetryableException(String message, Throwable throwable) {
        super(message, throwable);
    }

    public RetryableException(String message) {
        this(message, null);
    }
}

pom 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.6</version>
    <relativePath/>
</parent>
<groupId>ndhtest</groupId>
<artifactId>spring-retry-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
    <java.version>17</java.version>
    <groovy.version>4.0.5</groovy.version>
    <springboot.version>3.1.6</springboot.version>
    <lombok.version>1.18.30</lombok.version>
    <spock.version>2.4-M1-groovy-4.0</spock.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-core</artifactId>
        <version>${spock.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-spring</artifactId>
        <version>${spock.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.groovy</groupId>
        <artifactId>groovy</artifactId>
        <version>${groovy.version}</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${springboot.version}</version>
            <configuration>
                <mainClass>ndhtest.SpringBootConsoleApplication</mainClass>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>2.0.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compileTests</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M7</version>
            <configuration>
                <useModulePath>false</useModulePath> <!-- https://issues.apache.org/jira/browse/SUREFIRE-1809 -->
                <useFile>false</useFile>
                <includes>
                    <include>**/*Test</include>
                    <include>**/*Spec</include>
                </includes>
                    <statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
                    <disable>false</disable>
                    <version>3.0</version>
                    <usePhrasedFileName>false</usePhrasedFileName>
                    <usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
                    <usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
                    <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
                </statelessTestsetReporter>
            </configuration>
        </plugin>
    </plugins>
</build>
spring spring-boot spock spring-retry
1个回答
0
投票

至少有两种方法可以实现此目的:

  1. 使用

    @SpringBootTest
    ,不带
    classes
    参数。

  2. 在测试开始时添加

    RetrySynchronizationManager.register(Mock(RetryContext))
    ,如您还链接到的其他问题中here的建议。然后,您可以继续在
    classes
    中使用
    @SpringBootTest

© www.soinside.com 2019 - 2024. All rights reserved.