Spring-cloud-contract: StubRunner and a random port

Created on 15 Nov 2016  路  32Comments  路  Source: spring-cloud/spring-cloud-contract

Hello,
I would like to use StubRunner with a random port and bind this port (inside my test config) with a services that are supposed to call the stub.
I saw that it's possible to do with WireMock:
"To start the stub server on a different port use @AutoConfigureWireMock(port=9999) (for example), and for a random port use the value 0. The stub server port will be bindable in the test application context as "wiremock.server.port"."
Do you maybe populate some property with the port of the stub server that is used with StubRunner?
Thanks for help

bug enhancement

Most helpful comment

Is there anything happening with this? Those hacks are really nasty (creative tho) and I wish I shouldn't have to use them.

All 32 comments

You already can use StubRunner with random port. Just don't pass the port

@AutoConfigureStubRunner(ids="foo:bar:0.0.1.RELEASE:stubs")

and the port will be automatically picked. To get it back you can do it like this:

@Autowired StubFinder stubFinder;

int port = stubFinder.findStubUrl("bar").getPort();

I knew that it was possible to have a random port, but I have problems to bind it during the test with my configuration.
I have a client:

@FeignClient(name = "users", url = "${services.service1}", path = "/users")
public interface UsersClient {
....
}

with a url defined in the configuration file (depending on the profile):

services:
  service1: 'http://localhost:9999

In my test I would like to start the stub server based on the contracts and configure "UsersClient" to call this stub server. With a static port it's as simple as:

services:
  service1: 'http://localhost:9999

stubrunner:
  repositoryRoot: http://artifactory.path
  ids:
    - foo:bar:+:stubs:9999

Though when the port is dynamic, I need to somehow inject it inside my "UsersClient" configuration.
The neat way for me would be to be able to say:

services:
  service1: 'http://localhost:${stubrunner.server.port}

stubrunner:
  repositoryRoot: http://artifactory.path
  ids:
    - foo:bar:+:stubs

And let the spring-cloud to set stubrunner.server.port property while starting stub server (and refresh the context if needed).

I think it's the case for AutoConfigureWireMock. Based on the documentation: "The stub server port will be bindable in the test application context as "wiremock.server.port".

What do you think about such an idea?

Now I get it. That's a very good idea. We could do sth like (e.g. for artifactid: foo) stubrunner.foo.port, right?

Yes, exactly, that's what I was thinking about.

You could make it work with ${random.int} I should think. But we could provide first class support too somehow.

Thanks for help and waiting for the feature.

About the ${random.int}. It looked really promising, but it turned out that it will be generated each time we refer a property that contains it. To make it clear, let's take a look at this code:

random:
  port: ${random.int[1000,1500]}

services:
  service1: 'http://localhost:${random.port}

stubrunner:
  repositoryRoot: http://artifactory.path
  ids:
    - foo:bar:+:stubs:${random.port}

the value of the random.port inservices.service1 will be different than the random.port in stubrunner.ids, as random.int will be regenerated for each of the properties.

Enjoy @kkrawczy :) just call stubrunner.runningstubs.foo.port for a port of a server with artifactid equal to foo.

I've decided to add the runningstubs cause you might have an artifactid that overrides any of the existing stub runner properties

Thx a lot!
Though I fixed my problem before by injecting a "random port" from my pipeline build while running the tests.

@marcingrzejszczak Hi, I couldn't use something like the following:

@TestPropertySource(
        properties = {
                "urlForFoo=http://localhost:${stubrunner.runningstubs.foo.port}"}

I get "cannot resolve placeholder....." IllegalArgumentException.
It does not seem to be a limitation of TestPropertySource because other place holders work and only ${stubrunner.runningstubs.foo.port} does not.
I have this @TestPropertySource annotation on the same class as @AutoConfigureStubRunner - so is it because the property is not "ready" at the time Spring is resolving the place holders?

Yeah most likely the value binding happens after this property gets injected. Does this work when you inject it as a field in your test?

@marcingrzejszczak you mean "the value binding happens *before this property gets injected"? (or did I misunderstand something?)
I just checked and @Value("${stubrunner.runningstubs.myDependency-contracts.port}") works.
But the problem is - it is already too late because the "urlForFoo" property is already bound before this step and I cannot replace it anymore. It will be nice to have this work like the other available properties.

If it is not going to be possible, do you have a suggestion on a way to update an environment variable and at the same time also update the fields that are bound to it?

Yeah..... that seems to not work exactly as planned. I'll reopen this so as I don't forget about this.

As a workaround I suggest that you add a setter to a bean that needs this property injected so that you update it in the @Before section of your test. I know it's far from perfect but should unblock you

Thank you @marcingrzejszczak - I will try your suggestion.

Sth like this - https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/master/consumer/src/test/java/com/example/BeerControllerTest.java#L37 (but the port will be injected via @Value("${stubrunner.runningstubs.business-configuration-contracts.port}") )

unfortunately, I am not just testing the controller. I am testing a dependency (that calls its own dependent service). Anyways, I worked around that with ReflectionTestUtils for now - but its looking ugly at the moment. If you can help get the port thing going, it will be great.

@jot-hub I can't replicate it. I wrote a test and it's working fine - https://github.com/spring-cloud/spring-cloud-contract/commit/a6a8c79b75d1f32a630274e0d91458a2dc783ced . Can you please prepare a sample that I can take a look at to see what might be wrong?

@marcingrzejszczak You are right, both SpringBootTest properties and TestPropertySource properties work as long as the property that is getting set is injected in the same class. But if the same property is injected in a different bean and you are trying to set it with this particular place holder (other placeholders work) - it is not able to create that bean.
Here is the call stack of the failure:

2017-02-23 08:03:11,574 ERROR [main] - [org.springframework.boot.SpringApplication] [] - Application startup failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myController' defined in file [C:\myProject\myApp\target\classes\com\myCompany\myTeam\myComponent\controller\myController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myCompObjectService': Unsatisfied dependency expressed through field 'myHelper'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myHelper': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.myDependency-contracts.port' in string value "{'get_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}', 'post_myDependency': 'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys', 'delete_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}' }"
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:372)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
    at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
    at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myCompObjectService': Unsatisfied dependency expressed through field 'myHelper'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myHelper': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.myDependency-contracts.port' in string value "{'get_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}', 'post_myDependency': 'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys', 'delete_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}' }"
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
    ... 42 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myHelper': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.myDependency-contracts.port' in string value "{'get_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}', 'post_myDependency': 'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys', 'delete_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}' }"
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    ... 56 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.myDependency-contracts.port' in string value "{'get_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}', 'post_myDependency': 'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys', 'delete_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}' }"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:236)
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
    at org.springframework.core.env.AbstractPropertyResolver.resolveNestedPlaceholders(AbstractPropertyResolver.java:227)
    at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:83)
    at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:61)
    at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:530)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$1.getProperty(PropertySourcesPlaceholderConfigurer.java:132)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$1.getProperty(PropertySourcesPlaceholderConfigurer.java:129)
    at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:80)
    at org.springframework.core.env.PropertySourcesPropertyResolver.getPropertyAsRawString(PropertySourcesPropertyResolver.java:71)
    at org.springframework.core.env.AbstractPropertyResolver$1.resolvePlaceholder(AbstractPropertyResolver.java:239)
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:147)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:236)
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:172)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:831)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1086)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    ... 67 common frames omitted
2017-02-23 08:03:11,580 ERROR [main] - [org.springframework.test.context.TestContextManager] [] - Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@593aaf41] to prepare test instance [org.springframework.cloud.contract.verifier.tests.com.myCompany.myTeam.myComponent.contracts.QuoteIT@13809363]
java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
    at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
    at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myController' defined in file [C:\myProject\myApp\target\classes\com\myCompany\myTeam\myComponent\controller\myController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myCompObjectService': Unsatisfied dependency expressed through field 'myHelper'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myHelper': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.myDependency-contracts.port' in string value "{'get_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}', 'post_myDependency': 'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys', 'delete_myDependency':'http://localhost:${stubrunner.runningstubs.myDependency-contracts.port}/myDependency/s2s/v1/v1/myDependencys/{myDependencyId}' }"
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)

Ok and what if you create the config yourself. I mean sth like this (it's pseudocode).

@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(properties = ['foo=${stubrunner.runningstubs.fraudDetectionServer.port}'])
@AutoConfigureStubRunner
class StubRunnerConfigurationSpec extends Specification {

        // tests....

    @Configuration
    @EnableAutoConfiguration
    static class Config {

            @Value('${foo}') Integer foo

             MyController myController() {
               return new MyController("http://blablah:${foo}")
             }
        }
}

That's a perfectly valid approach. Other than that it would be really nice if you create a sample on Github that I could play with.

ok i will try it - Thank you. Regarding the sample project, I will try to set it up but not sure how quickly I can do it.

@marcingrzejszczak I tried your suggested approach - almost the same as the pseudo-code you provided. But it was not able to instantiate "Config" itself because of the failure in resolving ${foo} - the same exception occurs. The guess is the property is not available during the time Beans are instantiated. I think that you can reproduce the error now at your side with this static class Config approach.

just a short update: my colleague found a more elegant way - the stub ids (including the port) as well as the url used in the test are all set at the same time and with mutually compatible values in a static block (so gets set before everything) via properties & System.setProperty. I can live with this workaround for now.

Guys - can you check if current snapshots fix this issue? Maybe it's related to this fix - https://github.com/spring-cloud/spring-cloud-contract/issues/225 ?

@marcingrzejszczak sorry for the delay in a reply. I now tested this with "spring-cloud-starter-contract-stub-runner" 1.1.0.RELEASE and I continue to get the same error: java.lang.IllegalArgumentException: Could not resolve placeholder 'stubrunner.runningstubs.foo.port'

@jot-hub I'm having the same issue. How did you work around this? You mentioned you used a static block to set the properties. Do you have an example you can share? thanks

@kirsteend: here you go

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Blah.class})
@AutoConfigureStubRunner(
        ids = {
                "${stubs.id.foo}"
        }
)
public abstract class ContractBase {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @BeforeClass
    public static void classSetUp() throws Exception {
        int randomPort = ThreadLocalRandom.current().nextInt(10000, 65535);
        System.setProperty("stubs.id.foo", "mygroupid:foo:+:stubs:" + randomPort); //set the port yourself
        System.setProperty("foo.url", "http://localhost:" + randomPort); //<=== set the same port in the url used in actual artifact under test
    }
}

@jot-hub Worked perfectly! thanks!

We had the same problem on our side. In my understanding, the problem is that both beans, i.e. stub runner and the client (feign in this case), are managed within the context without an explicit dependency between them. Thus, the port determined while starting the wire mock instance cannot be used in the feign properties directly, except if set from the outside, e.g. using predefined random ports.

What we did to overcome this issue, is to use Spring expression language within the property value to refer to the bean factory, look up the stub finder and ask for its URL and port. This, of course, causes premature initialization of the stub runner/finder bean, but it solves the problem perfectly.

Another approach would be to use parent context, however, Spring Boot is tricky when using parent child context relations.

This, of course, causes premature initialization of the stub runner/finder bean, but it solves the problem perfectly.

@maverick1601 why do you think that it get prematurely initialized? You speed things up obviously but it's working fine right?

Can you also paste an example so others could see that?

I feel a bit uncomfortable, whenever I see SpEL within properties that triggers bean initialization of beans which themselves might refer to other properties as well. I don't think this is good practice in Spring contexts. Nevertheless, in that case it seemed to be the only way to solve this.

Here is an example properties file (YAML syntax) that should explain, how to grab the stub host/port from the stub finder:

provider.stub.artifact.id: contract-test-sample-rest-provider

stubrunner:
  ids: 'com.mycompany:${provider.stub.artifact.id}:0.0.1-SNAPSHOT:stubs'
  repositoryRoot: http://mynexus/

stub.host: '#{beanFactory.getBean(T(org.springframework.cloud.contract.stubrunner.StubFinder)).findStubUrl("${provider.stub.artifact.id}").getAuthority()}'

Is there anything happening with this? Those hacks are really nasty (creative tho) and I wish I shouldn't have to use them.

Nothing at this time. We're more than happy to accept PRs though :)

Hopefully it's fixed now :grimacing:

Was this page helpful?
0 / 5 - 0 ratings