New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Riptide Client Testing #301
Conversation
public MockRestServiceServer mockRestServiceServer(final RequestExpectationManager manager) throws Exception { | ||
final Constructor<MockRestServiceServer> ctr = MockRestServiceServer.class.getDeclaredConstructor(RequestExpectationManager.class); | ||
ctr.setAccessible(true); | ||
return ctr.newInstance(manager); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello Darkness My Old Friend
@@ -23,6 +23,11 @@ | |||
private Defaults defaults = new Defaults(); | |||
private GlobalOAuth oauth = new GlobalOAuth(); | |||
private Map<String, Client> clients = new LinkedHashMap<>(); | |||
private Boolean mocked = false; | |||
|
|||
public void setMocked(final Boolean mocked) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should get this for free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top Level of the Settings currently only has setters generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean getters, right? Isn't the constructor enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not get it working without this setter
somehow. He does not find a proper "writable" property path.
@@ -23,6 +23,11 @@ | |||
private Defaults defaults = new Defaults(); | |||
private GlobalOAuth oauth = new GlobalOAuth(); | |||
private Map<String, Client> clients = new LinkedHashMap<>(); | |||
private Boolean mocked = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not boolean
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting this here technically allows users to do this:
riptide:
mocked: true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can unbox - no particular reason to use Boolean
* | ||
* @return the components to test | ||
*/ | ||
@AliasFor(annotation = RestClientTest.class, attribute = "value") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any other attributes that are worth having as an alias?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't used any other on @RestClientTest
, but we may decided to just forward all of it?
@RestClientTest | ||
@AutoConfigureMockRestServiceServer(enabled = false) // see RiptideClientAutoConfiguration | ||
@TestPropertySource(properties = { | ||
"riptide.mocked: true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also referencing your comment above: any other method to inject the "mock mode" is obviously fine - this has been just the fastest way 😁
import java.io.IOException; | ||
import java.net.URI; | ||
|
||
public class ExpectingHttpRequestFactory implements AsyncClientHttpRequestFactory, ClientHttpRequestFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we use the existing implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is private. Yeah I know I am already using the private constructor of the server, but this guy here is 10 straight forward lines of code
Summary of this PR contains |
I think that would be cleaner, even without the dedicated artifact. Leaving the Registrar purely for production would be easier to understand for future changes. |
Are OAuth access tokens also mocked? Would be nice to avoid any external calls in the tests. |
Not explicitly. The |
The test setup could just register a |
d494498
to
d85f8b4
Compare
I've just pushed an alternative implementation that relies on the original trick from the The example above changes slightly, as there have to be one mocked server per client:
One important (?) thing to note is that the |
👍 |
d85f8b4
to
780bf90
Compare
Actually this is not needed. The |
} | ||
|
||
private String registerAsyncRestTemplate(final String id) { | ||
return registry.registerIfAbsent(id, AsyncRestTemplate.class, () -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That will incorrectly replace the AsyncRestTemplate
that we would register in the DefaultRegistrar
. It would miss plugins as well as the base url.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
}); | ||
} | ||
|
||
private String registerMockRestServiceServer(final String id, final String templateId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we register this once as a singleton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and share it across clients?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm I remember that we talked about having this as a singleton vs. per-client and somehow I was under the impression that I gave in to have it per-client 😬 I believe the singleton route is more intuitive either way!
@Autowired | ||
public TestService(@Qualifier("example") final Http http, @Qualifier("example") final RestTemplate restTemplate) { | ||
this.http = http; | ||
this.restTemplate = restTemplate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Access tokens are never used, but the default implementation will spawn a thread and poll tokens in the background. That could be disabled. |
@Documented | ||
@Inherited | ||
@RestClientTest | ||
@AutoConfigureMockRestServiceServer(enabled = false) // will be registered per client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is out-dated. Can you describe here why we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ups will do
settings.getClients().forEach((id, client) -> registerAsyncClientHttpRequestFactory(id, templateId, serverId)); | ||
} | ||
|
||
private String registerMockAsyncRestTemplate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't you register the template and the server using traditional @Bean
methods in the RiptideTestAutoConfiguration
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically yes, but I haven't found a cool way to still reference these beans in here on registering the request factories. Just by some explicit bean name? Any idea? I feel those 2 + (1 * n)
beans should go together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit bean name should work. I'd argue that the configuration binds those 2+n beans together. The Registrar is just our way to dynamically register the n beans.
} | ||
|
||
@Bean(name = SERVER_BEAN_NAME) | ||
MockRestServiceServer mockRestServiceServer(@Qualifier("_mockAsyncRestTemplate") final AsyncRestTemplate template) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The qualifier could use the TEMPLATE_BEAN_NAME
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ups forgot to replace
private final Registry registry; | ||
private final RiptideSettings settings; | ||
|
||
private final String asyncRestTemplateName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be fine with accessing the same constant.
👍 |
In a follow-up I will add a paragraph to the |
Hi, We had a use case where in This could be probably be done by Could you provide some hint how to do that? |
No easy way to do that right now. Options that I see:
|
Introducing
@RiptideClientTest
- a new spring-boot meta-annotation to support testing riptide-spring-boot-starter generated RiptideHttp
clients.Currently testing is a bit tedious and repetitive. It can be shorter:
@RiptideClientTest
is based on@RestClientTest
.Currently
@RiptideClientTest
and its facilities are tied to riptide-spring-boot-starter. In order to allow usage with riptide-core only, it may make sense to extract the components to a dedicated artifact. Instead of modifying theRiptideRegistar
directly, one would need to make sure that properly namedAsyncClientHttpRequestFactory
are registered beforehand (exposeRegistry#generateBeanName
?). Added benefit is that we wouldn't need anyprovided
testing dependencies anymore. On the other hand I am not sure whether anybody wants to / needs to use this without the starter...