Using Spring cloud contract to verify contract between my producer and consumer. In my consumer controller, I am using Feign client to call another micro-service method to get some data. But now in spring cloud contract making that stub call for this micro-service is impossible.
Using Spring Cloud with Netflix OSS.
Config-service and eureka is up. Now I installed my producer locally at port 8090. Consumer using Feign clients to call producer to get some data. Now I am getting 500 error. It is showing that URL not found. The closest match is /ping. I believe Feign client is unable to mock, it is somehow trying to connect with eureka not from the locally installed producer. Can you help me on it.
Any example or any idea will be great.
Thanks
It is possible, here is my JUnit test that does it (ParticipantsService uses a Feign Client)
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureStubRunner(ids = {"com.ryanjbaxter.spring.cloud:ocr-participants:+:stubs"}, workOffline = true)
#DirtiesContext
#ActiveProfiles("test")
public class OcrRacesApplicationTestsBase {
#Autowired
protected ParticipantsService participantsService;
private List<Participant> participants = new ArrayList<>();
//Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156
static {
System.setProperty("eureka.client.enabled", "false");
System.setProperty("spring.cloud.config.failFast", "false");
}
#Before
public void setup() {
this.participants = new ArrayList<>();
this.participants.add(new Participant("Ryan", "Baxter", "MA", "S", Arrays.asList("123", "456")));
this.participants.add(new Participant("Stephanie", "Baxter", "MA", "S", Arrays.asList("456")));
}
#After
public void tearDown() {
this.participants = new ArrayList<>();
}
#Test
public void contextLoads() {
List<Participant> participantList = participantsService.getAllParticipants();
assertEquals(participants, participantList);
}
}
Related
I have the Rabbit MQ broker for communicating asynchronously between services. Service A is sending messages to the queue. I checked the queue and the messages from Service A have arrived:
I am trying to create a listener in the Service B in order to consume the messages produced by Service A. I verified like below to check if Service B is connected with RabbitMQ and it seems to be connected successfully.
The problem is that Service B started successfully but it is receiving messages from Rabbit MQ.
Below is the implementation of the listener:
#Slf4j
#Component
public class EventListener {
public static final String QUEUE_NAME = "events";
#RabbitListener(
bindings = {
#QueueBinding(
value = #Queue(QUEUE_NAME),
exchange = #Exchange("exchange")
)
}
)
public void handleTaskPayload(#Payload String payload) {
System.out.println(payload);
}
}
I verified the queue and exchange information in the Rabbit MQ and they are correct.
Everything is working correctly and there is no error thrown in service A or service B which makes this problem much harder to debug.
I tried to retrieve the message from the queue getMessage of RabbitMQ the message is like the below:
{"id":"1",:"name:"Test","created":null}
I will appreciate any help or guidance towards the solution of this problem.
Best Regards,
Rando.
P.S
I created a new test queue like the below and published some messages:
Modified the listener code like below and still wasn't able to trigger listener to listen to the queue events:
#Slf4j
#Component
public class RobotRunEventListener {
public static final String QUEUE_NAME = "test";
#RabbitListener(
bindings = {
#QueueBinding(
value = #Queue(QUEUE_NAME),
key = "test",
exchange = #Exchange("default")
)
}
)
public void handleTaskPayload(#Payload String payload) {
System.out.println(payload);
}
Try this approach:
#RabbitListener(queues = "test")
public void receive(String in, #Headers Map<String, Object> headers) throws IOException {
}
The problem was that the spring boot app that I was working on had a #Conditional(Config.class) that prevented the creation of the bean below:
#Slf4j
#Conditional(Config.class)
#EnableRabbit
public class InternalRabbitBootstrapConfiguration {
#Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMaxConcurrentConsumers(5);
return factory;
}
...
which resulted in the spring boot app not listening to Rabbit MQ events. The Config.class required a specific profile in order to enable the app to listen to Rabbit MQ events.
public class DexiModeCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String[] activeProfiles = context.getEnvironment().getActiveProfiles();
return activeProfiles[0].equalsIgnoreCase(mode);
}
}
Hi am trying to write test for my Publisher class that publish the message to IBM queue using JMSTemplate and my test is as below. The test fails to execute the mock jmsTemplate method convertAndSend(). Not sure what I am doing wrong, but I couldn't find any support for testing the publisher as it is readily available in Spring Framework for other message brokers like rabbitMQ with Message Collectors.
Here is my code for the test:
'''
#SpringBootTest
#DirtiesContext
public class EventPublisherTest {
EventPublisher eventPublisher;
JmsTemplate jmsTemplate;
MessageCreator messageCreator;
Session mockSession;
#Before
public void setup() {
eventPublisher = Mockito.mock(EventPublisher.class);
jmsTemplate = Mockito.mock(JmsTemplate.class);
messageCreator = Mockito.mock(MessageCreator.class);
mockSession = Mockito.mock(Session.class);
}
#Test
public void testPublishEvent_PublishingMessage_Success() throws JMSException {
Mockito.when(mockSession.createTextMessage(Mockito.anyString())).thenReturn(Mockito.any());
Mockito.doAnswer(new Answer<Message>() {
#Override
public Message answer(InvocationOnMock invocation) throws Throwable {
final Object[] args = invocation.getArguments();
final MessageCreator arg = (MessageCreator)args[0];
return arg.createMessage(mockSession);
}
}).when(jmsTemplate).convertAndSend(Mockito.anyString());
eventPublisher.publishEvent(MessageBuilder.withPayload("mockMessage").build());
Mockito.verify(jmsTemplate, Mockito.times(1))
.convertAndSend(Mockito.anyString());
}
'''
You have just this eventPublisher = Mockito.mock(EventPublisher.class); and then there is no any stubs for that publishEvent() method call to make possible connection with that jmsTemplate.
There are no too much mocking tests with JmsTemplate because in most cases we do testing against embedded JMS broker like ActiveMQ.
Also I see that your Answer is not relevant to what you stub:
.when(jmsTemplate).convertAndSend(Mockito.anyString());
So, that InvocationOnMock may have only a string you send. No any MessageCreator is involved in the convertAndSend().
I'm not sure what are your expectations from this unit test, but at the moment it is just wrong and definitely cannot work.
BTW this has nothing to do with Spring Integration. Please, be careful next time when you choose tags for the question.
I'm in the process of building a new microservice and securing it with access tokens from Keycloak. So far I've been successful, the endpoint /token/test is only accessible with a valid token from Keycloak, the application properties look like this:
keycloak.auth-server-url=http://localhost:8888/auth
keycloak.realm=realm
keycloak.resource=api
keycloak.public-client=true
keycloak.securityConstraints[0].authRoles[0]=basic-token
keycloak.securityConstraints[0].securityCollections[0].name=secured by basic access token
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/*
This is working fine when starting the project with mvn spring-boot:run (I'm using spring-boot-starter, keycloak-spring-boot-starter and without spring-boot-starter-security which I want to avoid if possible.
Now I'm writing some tests for the fun of it and Keycloak's security constraints are simply not working. I've followed the test setup from https://github.com/cremich/testcontainers-keycloak (with updated versions and JUnit 5), the only difference being that the example is doing a lot of Keycloak setup by hand using spring-boot-starter-security. Why does it only work in tests when done with -security and why does my way not seem to work?
Am I missing something?
Thanks.
Edit: Example project
After some hours of debugging, I finally figured it out. The problem is that Keycloak's authentication is (for whatever reason, lol) done in a Tomcat valve, not in a filter. MockMvc doesn't go through the servlet container (and its valves), so it never even reaches the point where it would be authenticated.
TestRestTemplate does, though (and if you use starter-security, it is also a filter not a valve). I don't know the design decision behind using a valve and not a filter but you can either use the configuration from keycloak-starter and test it with a TestRestTemplate or use the more 'expensive' starter-security configuration in combination with MockMvc.
You need to set the authentication in SecurityContext with a mock or instance of the right type in each test: SecurityContextHolder.getContext().setAuthentication(authentication)
I wrote a set of libs to ease this. It includes a #WithMockKeycloackAuth annotation, along with Keycloak dedicated MockMvc request post-processor and WebTestClient configurer / mutator
Sample #WithMockKeycloackAuth usage:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = GreetingController.class)
#Import({
ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
KeycloakSpringBootSampleApp.KeycloakConfig.class })
// because this sample stands in the middle of non spring-boot-keycloak projects, keycloakproperties are isolated in
// application-keycloak.properties
#ActiveProfiles("keycloak")
public class GreetingControllerAnnotatedTest {
private static final String GREETING = "Hello %s! You are granted with %s.";
#MockBean
MessageService messageService;
#MockBean
JwtDecoder jwtDecoder;
#Autowired
MockMvcSupport api;
#Before
public void setUp() {
when(messageService.greet(any())).thenAnswer(invocation -> {
final var auth = invocation.getArgument(0, Authentication.class);
return String.format(GREETING, auth.getName(), auth.getAuthorities());
});
}
#Test
#WithMockKeycloakAuth
public void whenAuthenticatedWithoutAuthorizedPersonnelThenSecuredRouteIsForbidden() throws Exception {
api.get("/secured-route").andExpect(status().isForbidden());
}
#Test
#WithMockKeycloakAuth({ "AUTHORIZED_PERSONNEL" })
public void whenAuthenticatedWithAuthorizedPersonnelThenSecuredRouteIsOk() throws Exception {
api.get("/secured-route").andExpect(status().isOk());
}
#Test
#WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
id = #IdTokenClaims(sub = "42"),
oidc = #OidcStandardClaims(
email = "ch4mp#c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy"),
accessToken = #KeycloakAccessToken(
realmAccess = #KeycloakAccess(roles = { "TESTER" }),
authorization = #KeycloakAuthorization(
permissions = #KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
privateClaims = #ClaimSet(stringClaims = #StringClaim(name = "foo", value = "bar")))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
api.get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")))
.andExpect(content().string(containsString("TESTER")));
}
}
Different libs are available from maven-central, choose one of following according to your use-case:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>
Introduction
I would like to be able to have two different spring profiles, and depending on the profile to change to a hardcoded address for our feign builders.
Currently was have the following:
return builder.target(cls, "http://" + serviceName);
But I would actually like to do the following and over-ride the address:
return builder.target(cls, "http://our-server:8009/" + serviceName);
Why
Sometimes we don't want to run all the services within our development environment. Additionally, some of the services are only available through a zuul gateway sometimes.
So we run the same code in different situations and conditions.
Technical Details
We have the following code that we use for building our Feign Clients.
We had been using the #FeignClient annotation in the past, but lately we decided to start building our feignClients manually.
Example below:
#FeignClient(name = "ab-document-store", configuration = MultiPartSupportConfiguration.class, fallback = DocumentStoreFallback.class)
We call the feignRegistrar class with the following command:
return registerFeignClient(DocumentStoreClient.class, true);
#RequiredArgsConstructor
//#Component
#Slf4j
public class FeignRegistrar {
#Autowired
private Decoder decoder;
#Autowired
private Encoder encoder;
#Autowired
private Client client;
#Autowired
private Contract feignContract;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Autowired
private List<RequestInterceptor> interceptors;
public <T> T register(Class<T> cls, String serviceName, boolean isDocumentStore) {
if(isDocumentStore){
encoder = new MultipartFormEncoder(new SpringEncoder(messageConverters));
}
//Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
for(RequestInterceptor interceptor : interceptors) {
builder.requestInterceptor(interceptor);
}
log.debug("Registering {} - as feign proxy ", serviceName);
return builder.target(cls, "http://" + serviceName);
}
public static class Slf4Logger extends Logger {
#Override
protected void log(String configKey, String format, Object... args) {
log.info("{} - {}", configKey, args);
}
}
}
Spring Cloud Property Over-ride
We have also been using property files such as application-ENV.property with entries such as the following:
ab-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
ab-document-store.ribbon.listOfServers: localhost:8025
Unfortunately, listOfServers is not enough for us. We would like to be able to assign a directory/path as well. Something like:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
Otherworkaround
I have thought about sneaking in a header into all requests such as X-SERVICE-NAME using a feign interceptor. Then we could point all services to an address (e.g. localhost:9001) , and forward/proxy those requests to localhost:9001/X-SERVICE-NAME.
However, I would prefer a much easier solution such as:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
But this doesn't work :(
Introduction
I found a solution for this using a proxy that detects a header.
So, I have a feign interceptor on the java-side that attaches a header x-service-name to every feign-request.
I also have a NodeJS proxy, that analyzes requests, finds x-service-name, and re-writes the requests to become: x-service-name/originalRequestPath.
This allows me to have all the microservices behind a zuul gateway but also access them using a eureka-over-ride.
Java-Feign-Interceptor
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(usedEncoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
NodeJS proxy
In the example, my zuul gateway ( or another proxy ) is on localhost:9001.
I'm listening on localhost:1200 .
let enableProxyForJava = process.env.ENABLE_PROXY_FOR_JAVA;
if (enableProxyForJava != undefined && enableProxyForJava.toLowerCase() === 'true') {
var httpProxyJava = require('http-proxy');
var proxyJava = httpProxyJava.createProxy();
gutil.log( gutil.colors.green('Enabling Proxy for Java. Set your Eureka overrides to localhost:1200.') );
require('http').createServer(function(req, res) {
console.log("req.headers['x-service-name'] = " + req.headers['x-service-name']);
console.log("Before req.url:"+ req.url);
if( req.headers['x-service-name'] != undefined){
let change = req.headers['x-service-name'] +req.url;
console.log("After req.url:"+ change);
req.url = change;
}
proxyJava.web(req, res, {
target: 'http://localhost:9001/'
});
}).listen(1200);
}
Property file inside Java Application that has feign clients
mbak-microservice1.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice1.ribbon.listOfServers: localhost:1200
mbak-microservice2.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice2.ribbon.listOfServers: localhost:1200
mbak-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-document-store.ribbon.listOfServers: localhost:1200
Hello I am trying to create a dirty test for my soap integration test. I just got SSL working on my spring boot app and I wanted to see if it will hit my soap end point.
When I run man verify on my integration test I get this error:
com.sun.xml.messaging.saaj.SOAPExceptionImpl: Invalid Content-Type:text/plain. Is this an error message instead of a SOAP response?
Here is my test code :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {EndPointTestConfiguration.class
})
public class SoapIT {
private static ApplicationContext context;
#BeforeClass
static public void setup(){
SpringApplication springApplication = new SpringApplicationBuilder()
.sources(MockServerApp.class)
.build();
context = springApplication.run();
}
#Autowired
private String studyDetailDemo;
#Test
public void soapTest() throws ClientProtocolException, IOException {
String result = Request.Post("https://127.0.0.1:28443/nulogix/ws/billingtool")
.connectTimeout(2000)
.socketTimeout(2000)
.bodyString(studyDetailDemo, ContentType.TEXT_PLAIN)
.execute().returnContent().asString();
}
}
I am kind of new to integration testing and have no idea what this error means
Thank you for any help
I think you need to read up a bit on how to do spring testing.
testing-with-mock-environment
#SpringBootTest will automatically scan for spring annotated classes and load up a mockspringcontext for you so you dont need to do all the #BeforeClass things.
If you want to call this "Mock context" you need configure and autowire in a MockMvc, WebTestClient or TestRestTemplate.
On the other hand if you want to start a real server you need to specify #SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) (or a defined port).
You can read all about it in the linked documentation above.
And btw, you can't autowire in a string.
Your code should look something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class SoapIT {
#LocalServerPort
private int port;
private String studyDetailDemo = "some body text";
#Test
public void soapTest() throws ClientProtocolException, IOException {
String result = Request.Post("https://127.0.0.1:" + port + "/nulogix/ws/billingtool")
.connectTimeout(2000)
.socketTimeout(2000)
.bodyString(studyDetailDemo, ContentType.TEXT_PLAIN)
.execute().returnContent().asString();
}
}
Havn't tried the code, wrote it on mobile.