Модульное тестирование Flux.doOnNext()

#java #spring-boot #unit-testing #spring-webflux #project-reactor

Вопрос:

У меня есть метод, который принимает поток в качестве параметра. Внутри метода он затем настраивает поток, и другой метод подписывается на него.

 public class StringProcessor {

  private final stringParsingService stringParsingService;

  public void subscribeStringFlux(Flux<String> stringFlux) {
    fluxConfiguration(stringFlux)
        .subscribe();
  }

  Flux<String> fluxConfiguration(Flux<String> stringFlux) {
    return stringFlux
        .filter(stringValidatorr::isValidString)
        .doOnNext(itemString -> {
          List<String> values = stringParsingService.parseValues(itemString);
        })
        .onErrorContinue((e,object) -> log.error(e.getClass().toString() " " e.getMessage()));
  }
}
 

Я пытаюсь проверить, что код в doOnNext выполнен. Я пытался использовать StepVerifier (с Mockito для службы), однако, кажется, что он никогда не вводит код во время теста, когда я помещаю точки останова в stringParsingService.parseValue(). Тем не менее, код действительно выполняется и выполняется должным образом, хотя и без выполнения в тесте с реальными данными.

Мой вопрос в том, как вы пишете тесты, которые охватывают действия, выполняемые в Flux.doOnNext()? Есть ли способ использовать StepVerifier, который позволит ему выполнять код в doOnNext()? Я искал несколько дней, пробовал несколько подходов, и до сих пор ни один из них не сработал.

Единственный способ, который я нашел до сих пор, который даже близко подходит, — это сделать следующее (однако это, конечно, не учитывается для покрытия кода).:

     Flux<String> testStringFlux = Flux.just("a_test_string");

    StepVerifier.create(testStringFlux)
        .consumeNextWith(itemString -> {
           List<String> values = stringParsingService.parseValues(itemString);
        })
        .verifyComplete();
 

Комментарии:

1. Вы создаете тестовую строку, вызываете свою функцию с помощью этой тестовой строки. Функция возвращает поток. Вы запускаете этот поток в пошаговом преобразователе, проходите через него и утверждаете результаты.

Ответ №1:

doOnNext является побочным оператором, что означает, что выполняемая им работа на самом деле не видна с точки зрения основной реактивной последовательности (the Flux<T> ). В результате ни один из инструментов, используемых для тестирования a Flux , не может действительно увидеть и протестировать побочный эффект, если вы не сделаете его тестируемым явно.

Одним из возможных способов было бы сделать StringProcessor.stringParsingService используемый в вашем тесте макет или экземпляр для конкретного теста, который записывает проанализированные строки, а затем утверждает это в конце Flux последовательности.

Обратите внимание , что вы doOnNext вычисляете a List , но после этого этот список не используется. Излучаемые элементы someFlux.doOnNext(function) точно такие же , как и излучаемые someFlux , независимо от того, что делается внутри function (если function не считать бросков, но это уже другая история).

Комментарии:

1. Спасибо вам за ответ. Мне кажется странным, что побочные эффекты не поддаются проверке и являются своего рода слепым пятном в функциональности StepVerifier.

2. это сродни высказыванию «Я хочу проверить эффект выполнения a ExecutorService.submit(Runnable) «, если вам нужна точка сравнения с классом JDK.

Ответ №2:

если вы настроите свой поток следующим образом, он остановится в точке останова функции parseValues. Но я не уверен, что это то, о чем вы просите…

pom.xml

 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>flux_test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.4.8</version>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
            <version>3.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>
 

StringProcessor.java

 import org.apache.log4j.Logger;
import reactor.core.publisher.Flux;

import java.util.Arrays;
import java.util.List;

public class StringProcessor {

    private final static Logger log = Logger.getLogger(StringProcessor.class.getName());

    private class StringParsingService {
        List<String> parseValues(String str) {
            return Arrays.asList(str.split(","));
        }
    }

    private static class StringValidator {
        static boolean isValidString(String str) {
            return str.length() > 1;
        }
    }

    private final StringParsingService stringParsingService = new StringParsingService();

    public void subscribeStringFlux(Flux<String> stringFlux) {
        fluxConfiguration(stringFlux)
                .subscribe();
    }

    Flux<String> fluxConfiguration(Flux<String> stringFlux) {
        return stringFlux
                .filter(StringValidator::isValidString)
                .doOnNext(itemString -> {
                    List<String> values = stringParsingService.parseValues(itemString);
                })
                .onErrorContinue((e, object) -> log.error(e.getClass().toString()   " "   e.getMessage()));
    }

}
 

TestFlux.java

 import org.junit.Test;
import reactor.test.StepVerifier;
import reactor.test.publisher.TestPublisher;

public class TestFlux {

    final TestPublisher<String> testPublisher = TestPublisher.create();

    @Test
    public void testFlux() {
        StepVerifier.create(new StringProcessor().fluxConfiguration(testPublisher.flux()))
                .then(() -> testPublisher.emit("aa,bb", "cc,dd"))
                .expectNext("aa,bb")
                .expectNext("cc,dd")
                .verifyComplete();
    }

}
 

Комментарии:

1. Спасибо, что нашли время ответить,когда я запускаю тест так, как вы написали, просто выдавая первую строку и только один ожидаемый текст(«aa, bb») Я получаю ошибку утверждения «ожидаемое: onNext(«aa,bb») фактическое: onComplete()». Я использую Junit 5, но в остальном моя настройка точно такая же, как у вашего pom (я использую реактор Spring Boot). Кроме того, да, я хочу иметь возможность достичь точки останова в методе parseValues во время тестирования.

2. Если вы хотите выпустить и ожидаете только один элемент, вы можете изменить эмиттер на testPublisher.emit(«aa,bb») , тогда он будет успешным. В обоих случаях вы должны иметь возможность использовать точки останова, если запускаете тесты в режиме отладки…