spring-cloud / spring-cloud-stream

Framework for building Event-Driven Microservices
http://cloud.spring.io/spring-cloud-stream
Apache License 2.0
1.01k stars 613 forks source link

testSendAndReceiveNoOriginalContentType in AbstractBinderTests Fails Content Type Assert Check #2648

Closed linny0608 closed 1 year ago

linny0608 commented 1 year ago

In the test the the MessageBuilder.setHeader call is being made with the MimeTypeUtils.TEXT_PLAIN value. When using this Mime Type value the message does not get sent with that specified content type and always comes back as application/json instead. So as a test I changed the MessageBuilder.setHeader call to use the MimeTypeUtils.TEXT_PLAIN_VALUE instead and the message content type is being changed to text/plain with this change. Can someone explain why is the test failing if MimeTypeUtils.TEXT_PLAIN is used as the value verses the use of MimeTypeUtils.TEXT_PLAIN_VALUE?

sobychacko commented 1 year ago

@linny0608 What binder is this test for?

sobychacko commented 1 year ago

The headers need to be properly copied in the binder implementation. Kaka and Rabbit binder implementations take care of that.

linny0608 commented 1 year ago

@sobychacko I am trying to test a custom binder that we are trying to create and I was trying extend from the AbstractBinderTests to test our binder, but I have that one test that fails when it checks on the content type.

sobychacko commented 1 year ago

You can override that test in your implementation and then set the expectation against application/json. We had to do that in the Pulsar binder implementation. We added some comments above that assertion as a TODO.

linny0608 commented 1 year ago

@sobychacko Maybe you can also help me with this question also. When setting up the test environment to test our custom binder in our project I have a test directory that contains the test that we want to run to test our binder and within that directory I also have a resources folder that I have created an application.properties file. Is there some annotation that needs to be added to these test files for it to automatically pick up the application.properties file so that it can detect certain configuration properties?

linny0608 commented 1 year ago

@sobychacko Why does the content type not work when we do the following command

Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,**MimeTypeUtils.TEXT_PLAIN**).build();

But if I do the same command but change the MimeTypeUtils value to TEXT_PLAIN_VALUE instead the assert check passes.

Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,**MimeTypeUtils.TEXT_PLAIN_VALUE**).build();
sobychacko commented 1 year ago

Those properties will not be picked up in these tests (unless they are Boot-based tests which these tests are not). You may want to do something similar to this.

sobychacko commented 1 year ago

I am not sure why MimeTypeUtils.TEXT_PLAIN is not working in your case. Can you debug around that code and find out what's going on there?

linny0608 commented 1 year ago

@sobychacko So I started to debug the code to see what the difference is when I send MimeTypeUtils.TEXT_PLAIN verses MimeTypeUtils.TEXT_PLAIN_VALUE. The strange thing that I am seeing when I debug the code is that in both cases when I look at the header value for the message variable the content type value for both show that it is text/plain. However, when I check the inboundMessageRef variable headers information for the contentType when using the MimeTypeUtils.TEXT_PLAIN it is showing as "application/json" and the run when I use MimeTypeUtils.TEXT_PLAIN_VALUE it is showing as "text/plain". So it seems like something strange is happening when it does the following lines of code in the test when I send a MimeType verses a string value to indicate the content type to use:

moduleInputChannel.subscribe(message1 -> {
      try {
        inboundMessageRef.set((Message<byte[]>) message1);
      } finally {
        latch.countDown();
      }
    });

Do you know what could be causing this odd behavior?

sobychacko commented 1 year ago

We need to troubleshoot further to identify the issue. I would compare the same test with Kafka binder (or Rabbit) and see where exactly your binder diverges.

sobychacko commented 1 year ago

@linny0608 Are there any updates from your side on this issue? Would you like for us to look at anything further on this?

linny0608 commented 1 year ago

@sobychacko These test still seems to have an issue. We still need to figure out why it keeps failing. Seems strange using different MimeTypeUtils.TEXT_PLAIN verses MimeTypeUtils.TEXT_PLAIN_VALUE gives different results.

linny0608 commented 1 year ago

In the process of updating to the spring boot 3.0 and dependency versions and now I am getting this particular error with the test file that I am extending from the AbstractBinderTests class: "The type org.springframework.cloud.stream.binding.StreamListenerMessageHandler cannot be resolved. It is indirectly referenced from required type org.springframework.cloud.stream.binder.AbstractBinderTests". Do you have any idea how to resolve this issue?

sobychacko commented 1 year ago

StreamListenerMessageHandler is long deprecated and removed from the framework. Do you have more errors or stacktrace to share?

linny0608 commented 1 year ago

I am updated the spring-cloud-stream-binder-test version to 3.2.7 and when I look at the AbstractBinderTests class in there I can see that the file is using StreamListenerMessageHandler.

sobychacko commented 1 year ago

Ya, in 3.2.7 it was probably there, although it was deprecated. In 4.0.x, it is removed.

linny0608 commented 1 year ago

The highest version I see in maven at this time is 3.2.7. I don't see 4.0.x version available.

linny0608 commented 1 year ago

Okay I fixed the problem by lowering the version for the spring-cloud-stream jar to 3.2.7 instead of 4.0.2

linny0608 commented 1 year ago

I was able to run these test before updating the versions in the pom file. I fixed the issue with the StreamListenerMessageHandler class, but now all the tests are failing with the following error.

java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@3e821657 testClass = com.oracle.stream.TxEventQBinderTests, locations = [], classes = [com.oracle.cstream.config.TxEventQJmsConfiguration, com.oracle.cstream.config.JmsBinderAutoConfiguration, com.oracle.stream.TxEventQBinderTests], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = ["file:src/test/java/com/oracle/stream/resources/application-test.properties"], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7ac296f6, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@54e041a4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@776a6d9b, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@9da1, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@928763c, org.springframework.boot.test.context.SpringBootTestAnnotation@457249f6], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
 at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext([DefaultCacheAwareContextLoaderDelegate.java:142](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at org.springframework.test.context.support.DefaultTestContext.getApplicationContext([DefaultTestContext.java:127](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary([ServletTestExecutionListener.java:191](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance([ServletTestExecutionListener.java:130](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at org.springframework.test.context.TestContextManager.prepareTestInstance([TestContextManager.java:241](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance([SpringExtension.java:138](vscode-file://vscode-app/c:/Users/Linh/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at java.base/java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
 at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
 at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source)
 at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
 at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
 at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(Unknown Source)
 at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Unknown Source)
 at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Unknown Source)
 at java.base/java.util.stream.ReferencePipeline$Head.forEach(Unknown Source)
 at java.base/java.util.Optional.orElseGet(Unknown Source)
 at java.base/java.util.ArrayList.forEach(Unknown Source)
 at java.base/java.util.ArrayList.forEach(Unknown Source)
linny0608 commented 1 year ago

@sobychacko Besides the exception "Failed to load ApplicationContext ..." that I mentioned in the prior comment I am also now getting this log error. Do you have any ideas what could be causing the test to not run now when it was running fine awhile ago for me? I am able to use my binder in an application to produce and consume fine at the moment, but the test are all failing right now.

Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.helpers.NOPLoggerFactory loaded from file:/C:/Users/Linh/.m2/repository/org/slf4j/slf4j-api/1.7.33/slf4j-api-1.7.33.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.helpers.NOPLoggerFactory
sobychacko commented 1 year ago

@linny0608 Can you provide us with a small sample project where we can reproduce the issue you are seeing? As an aside, please use code fencing when you put code or stack trace so they are formatted. I manually edited your comments above with code fencing.

linny0608 commented 1 year ago

@sobychacko I was able to fix the above issue by adding the following exclusion to my dependency as shown below:

`

org.springframework.boot
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
        <version>3.0.5</version>
    </dependency>

`

However, now when the test I get this NoSuchMethodError for all the test. java.lang.NoSuchMethodError: 'void org.springframework.web.context.ConfigurableWebApplicationContext.setServletContext(jakarta.servlet.ServletContext)'

Is there a dependency that I am now missing or am I using a version that doesn't support this method anymore? Thanks again for all the help you have provided so far.

sobychacko commented 1 year ago

Is this still happening from your usage of AbstractBinderTests? On the application side, you shouldn't need to do that exclusion. So, I am surprised that you need to do that. Is this a new binder implementation? Anyways, if you can provide any small sample projects where these issues occur, we maybe able to help further.,

linny0608 commented 1 year ago

@sobychacko I was able to fix the above issue by adding the following exclusion to my dependency as shown below:

`

org.springframework.boot
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
        <version>3.0.5</version>
    </dependency>

`

However, now when the test I get this NoSuchMethodError for all the test. java.lang.NoSuchMethodError: 'void org.springframework.web.context.ConfigurableWebApplicationContext.setServletContext(jakarta.servlet.ServletContext)'

Is there a dependency that I am now missing or am I using a version that doesn't support this method anymore? Thanks again for all the help you have provided so far.

linny0608 commented 1 year ago

@sobychacko I just recently found this posting https://github.com/spring-cloud/spring-cloud-stream/issues/2346 that says that TestSupportBinder is deprecated. I believe I am maybe trying to use this to run some binder test. This is the testing dependency that I currently have in my project.

      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-test</artifactId>
            <version>3.2.7</version>
            <scope>test</scope>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>3.0.2</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream</artifactId>
            <version>3.2.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
             <version>3.0.5</version>
            <scope>test</scope>
        </dependency>
          <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-test-support</artifactId>
            <version>4.0.1</version>
            <scope>test</scope>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>3.0.5</version>
            <optional>true</optional>
        </dependency>       

Can I still use this to help with the testing of the binders? If not can you point me to what can be used?

sobychacko commented 1 year ago

Although it is deprecated, I think this can still be used in the 3.2.x line. But in 4.0.x we removed the TestSupportBinder in favor of the message channel-based test binder. In 4.0.x, Spring Cloud Stream provides the test binder in spring-cloud-stream-test-binder module and all the normal test support for downstream binders through spring-cloud-stream-test-support.

linny0608 commented 1 year ago

The the dependency that I currently have set above I seem to be getting an IO exception of some kind when it is trying get the configuration files. So currently the test that I want to run just extends the AbstractBinderTests class and I have the following declared in the test class: @SpringBootTest( classes = { TxEventQJmsConfiguration.class, JmsBinderAutoConfiguration.class, TxEventQBinderTests.class, } )

I am getting the following error when I have TxEventQJmsConfiguration.class or JmsBinderAutoConfiguration.class declared in the @SpringBootTest: java.lang.NoClassDefFoundError: org/springframework/core/NestedIOException at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:323) at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:59) at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:47) at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1388) at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:545) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:184) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:118) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:241) at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:377) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:382) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:377) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735) at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:376) I tried to resolve this NoClassDefFound error, but adding certain version levels to fix this issue would then cause errors because other methods are not available with that particular version. It was running when I initially had it set to use spring boot 2.22, but then we upgraded to spring boot 3.0.5 and test don't work anymore with all the dependency version updates.

sobychacko commented 1 year ago

Is the code you are testing available publicly? I am still unable to figure out why it is not working for you. Kafka and Rabbit binder tests extend from the AbstractBinderTests without issues. You should look there and see how your code is diverging from them.

linny0608 commented 1 year ago

So looking at the Kafka binder I noticed that the test are not using any of the annotations such as "@SpringBootTest". Should I not be doing this in my test? Here is an example of what my test file looks like.

` @SpringBootTest( classes = { QJmsConfiguration.class, JmsBinderAutoConfiguration.class, QBinderTests.class, } ) @TestPropertySource( locations = "file:src/test/java/com/xxx/stream/resources/application-test.properties" ) public class QBinderTests extends AbstractBinderTests<QTestBinder, ExtendedConsumerProperties, ExtendedProducerProperties> {

@Autowired private JMSMessageChannelBinder jmsMessageChannelBinder;

@Override protected ExtendedConsumerProperties createConsumerProperties() { return new ExtendedConsumerProperties( new JmsConsumerProperties() ); }

@Override protected ExtendedProducerProperties createProducerProperties( TestInfo testInfo ) { return new ExtendedProducerProperties( new JmsProducerProperties() ); }

@Override protected QTestBinder getBinder() throws Exception { QTestBinder testBinder = new QTestBinder(); testBinder.setBinder(this.jmsMessageChannelBinder); return testBinder; }

@Override public Spy spyOn(String name) { throw new UnsupportedOperationException("'spyOn' is not used by JMS tests"); }

@Override protected String getDestinationNameDelimiter() { return "_"; } }`

sobychacko commented 1 year ago

Yes. I would mimic exactly what is done on the Kafka binder tests, as these binder tests are given as a template for verifying the basic binder contracts.

sobychacko commented 1 year ago

@linny0608 Are there any updates to this issue? Are you still facing the same issues? I wonder if we can close the issue now.

linny0608 commented 1 year ago

Yes, you can close this issue.