How can I make WireMock port more dynamic to use it for testing service - spring

I am using wiremock to mock github api to do some testing of my service.
The service calls github api. For the tests I am setting endpoint property to
github.api.endpoint=http://localhost:8087
This host and port are the same as wiremock server #AutoConfigureWireMock(port = 8087) so I can test different scenarios like : malformed response, timeouts etc.
How can I make this port dynamic to avoid case when it is already used by system ? Is there a way to get wiremock port in tests and reassign endpoint property ?
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 8087)
#TestPropertySource(properties ={"github.api.endpoint=http://localhost:8087"})
public class GithubRepositoryServiceTestWithWireMockServer {
#Value("${github.api.client.timeout.milis}")
private int githubClientTimeout;
#Autowired
private GithubRepositoryService service;
#Test
public void getRepositoryDetails() {
GithubRepositoryDetails expected = new GithubRepositoryDetails("niemar/xf-test", null,
"https://github.com/niemar/xf-test.git", 1, "2016-06-12T18:46:24Z");
stubFor(get(urlEqualTo("/repos/niemar/xf-test"))
.willReturn(aResponse().withHeader("Content-Type", "application/json").withBodyFile("/okResponse.json")));
GithubRepositoryDetails repositoryDetails = service.getRepositoryDetails("niemar", "xf-test");
Assert.assertEquals(expected, repositoryDetails);
}
#Test
public void testTimeout() {
GithubRepositoryDetails expected = new GithubRepositoryDetails("niemar/xf-test", null,
"https://github.com/niemar/xf-test.git", 1, "2016-06-12T18:46:24Z");
stubFor(get(urlEqualTo("/repos/niemar/xf-test"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile("/okResponse.json")
.withFixedDelay(githubClientTimeout * 3)));
boolean wasExceptionThrown = false;
try {
GithubRepositoryDetails repositoryDetails = service.getRepositoryDetails("niemar", "xf-test");
} catch (GithubRepositoryNotFound e) {
wasExceptionThrown = true;
}
Assert.assertTrue(wasExceptionThrown);
}

You have to set the WireMock port to 0 so that it chooses a random port and then use a reference to this port (wiremock.server.port) as part of the endpoint property.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
#TestPropertySource(properties = {
"github.api.endpoint=http://localhost:${wiremock.server.port}"
})
public class GithubRepositoryServiceTestWithWireMockServer {
....
}
See also Spring Cloud Contract WireMock.

I know this is a bit old post but still there is a documented way to have these ports dynamically. Read more here: Getting started. Just scroll down a bit to 'Random port numbers'.
From the documentation there:
What you need to do is to define a Rule like so
#Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort());
And then access them via
int port = wireMockRule.port();
int httpsPort = wireMockRule.httpsPort();

One more way, you can use dynamic port without conflict is
import org.springframework.util.SocketUtils;
int WIREMOCK_PORT = SocketUtils.findAvailableTcpPort();
public WireMockRule wireMockServer = new WireMockRule(WIREMOCK_PORT);
if you want to access it from properties file, then we have wiremock.server.portprovided by Wiremock
"github.api.endpoint=http://localhost:${wiremock.server.port}"

I am not aware of #AutoConfigureWireMock but if you are manually starting wiremock and setting up mocks, while starting spring you can setup a random port number utilizing spring random. A sample will look like this
in your wiremock class
#Component
public class wiremock {
#Value("${randomportnumber}")
private int wiremockPort;
public void startWiremockServer() {
WireMock.configureFor("localhost", wiremockPort);
wireMockServer = new com.github.tomakehurst.wiremock.WireMockServer(wireMockConfig().port(wiremockPort).extensions
(MockedResponseHandler.class));
wireMockServer.start();
}
}
In your test class
//however you want to configure spring
public class wiremock {
#Value("${github.api.endpoint}")
private String wiremockHostUrl;
//use the above url to get stubbed responses.
}
in your application.properties file
randomportnumber=${random.int[1,9999]}
github.api.endpoint=http://localhost:${randomportnumber}

Related

Better way to set testcontainer properties from default applicaton.yml in springboottest

I am using posgresql testcontainer in springboottest. As I have multiple tests involving this testcontainer, hence I have used static testcontainer which will be invoked once for all tests of 1 junit class and shutdown after all tests are executed.
This I have implemented using ParameterResolver, BeforeEachCallback.
Problem with this approach is that datasource metadata like jdbc-url, db name , host , port configured in default application.yml is not used directly in testcontainer properties, instead I have hardcoded those values because springboot properties are not available at that time.
is there any better approach where I can use static testcontainers having BeforeEachCallback feature whose values are fetched from default application.yml ?
#SpringBootTest
class SampleTest extends TestContainerBase {
#Test
void test1() {
//some code
}
}
#ExtendWith(ContainerExtension.class)
#ResourceLock(Environment.ID)
public abstract class TestContainerBase {
protected static String jdbcUrl;
protected static String username;
protected static String password;
#BeforeAll
static void prepareContainerEnvironment(Environment env) {
jdbcUrl = env.getJdbcUrl();
username = env.getUsername();
password = env.getPassword();
}
#DynamicPropertySource
static void dynamicPropertySource(DynamicPropertyRegistry registry) {
registry.add("spring.datasource-.jdbc-url", () -> jdbcUrl);
registry.add("spring.datasource-.username", () -> username);
registry.add("spring.datasource-.password", () -> password);
registry.add("spring.datasource-.driver-class-name", () -> "org.postgresql.Driver");
}
}
public class ContainerExtension implements ParameterResolver, BeforeEachCallback {
// overridden supportsParameter and resolveParameter
}
I want that myDB , sa , sa are read from application.yml. How can I get application.yml values here in this class ? As springboot context is not yet loaded so I am unable to think of any alternative to get those values.
public class ContainerResource extends Environment {
#Container
protected static PostgreSQLContainer postgreSQLContainer =
new PostgreSQLContainer("artifactory.devtools.syd.c1.macquarie.com:9996/postgres:11")
.withDatabaseName("myDB")
.withUsername("username")
.withPassword("password");
ContainerEnvironmentResource() {
postgreSQLContainer.start();
this.setJdbcUrl(postgreSQLContainer.getJdbcUrl());
this.setUsername(postgreSQLContainer.getUsername());
this.setPassword(postgreSQLContainer.getPassword());
}
}
It looks like there is now a dedicated project just to integrate Testcontainers and Spring-Boot. As I understand the documentation it should be transparent to the code as everything is done using Spring magic.
https://github.com/Playtika/testcontainers-spring-boot

Feign with RibbonClient and Consul discovery without Spring Cloud

I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.
In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.

Test sending email in Spring

I want to test my services in spring which should send emails.
I try to use org.subethamail:subethasmtp.
To acieve my goal I created service MySender where I send email:
#Autowired
private MailSender mailSender;
//...
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("example#example.com");
message.setSubject("Subject");
message.setText("Text");
mailSender.send(message);
// ...
To test this piece of code I created test application.properties (in test scope):
spring.mail.host=127.0.0.1
spring.mail.port=${random.int[4000,6000]}
And test configuration class which should start Wiser SMTP server and make it reusable in tests:
#Configuration
public class TestConfiguration {
#Autowired
private Wiser wiser;
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Bean
public Wiser provideWiser() {
// provide wiser for verification in tests
Wiser wiser = new Wiser();
return wiser;
}
#PostConstruct
public void initializeMailServer() {
// start server
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
}
#PreDestroy
public void shutdownMailServer() {
// stop server
wiser.stop();
}
}
Expected result is that application sends email using Wiser smtp server and verify number of sended messages.
But when I run service application throws MailSendException(Couldn't connect to host, port: 127.0.0.1, 4688; timeout -1;).
But when I add breakpoint and try connect using telnet smtp server allow to connect and don't throw Connection refused.
Do you have any idea why I can't test sending mails?
Full code preview is available on github:
https://github.com/karolrynio/demo-mail
I faced same problem. If using some constant port number for spring.mail.port in test Spring configuration combined with Maven tests forking, it resulted in tests randomly failing on port conflict when starting Wiser.
As noted here in comments, using random.int doesn't help - it returns different value each time it's referenced, and it's expected behavior (see this issue).
Hence, we need a different way to initialize spring.mail.port with a random value, so it would be constant within the test execution. Here's a way to do it (thanks for advice here):
First, we may not set spring.mail.port in test properties file at all. We'll initialize it in TestPropertySource. We'll need a class like this:
public class RandomPortInitailizer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
int randomPort = SocketUtils.findAvailableTcpPort();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
"spring.mail.port=" + randomPort);
}
}
Now we can run our tests this way (not too different from what's found in OP):
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = RandomPortInitailizer.class)
public class WhenEmailingSomeStuff {
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Before
public void startEmailServer() {
wiser = new Wiser();
wiser.setPort(smtpPort);
wiser.setHostname(smtpHost);
wiser.start();
}
#After
public void stopEmailServer() {
wiser.stop();
}
#Test
public void testYourJavaMailSenderHere() {
//
}
}
in the application properties can you also add
mail.smtp.auth=false
mail.smtp.starttls.enable=false
The change your code to have these extra two values
#Value("${mail.smtp.auth}")
private boolean auth;
#Value("${mail.smtp.starttls.enable}")
private boolean starttls;
and put these options in your initializeMailServer
Properties mailProperties = new Properties();
mailProperties.put("mail.smtp.auth", auth);
mailProperties.put("mail.smtp.starttls.enable", starttls);
wiser.setJavaMailProperties(mailProperties);
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
let me know if this worked for you

how to change the #FeignClient name in runtime

I use Spring Cloud Netflix to build my micro service .
#FeignClient(name = "ms-cloud",configuration = MsCloudClientConfig.class)
public interface TestClient {
/**
* #return
*/
#RequestMapping(value = "/test", method = RequestMethod.GET)
String test();
}
I want to change the name to ms-cloud-pre when some special user.
Anyone can give some advice?
According to the documentation feign supports placeholders in the name and url fields.
#FeignClient(name = "${store.name}")
public interface StoreClient {
//..
}
So you could set store.name=storeProd at runtime using normal spring boot configuration mechanisms.
To create a spring-cloud Feign client at runtime in situations where you don't know the service-id until the point of call:
import org.springframework.cloud.openfeign.FeignClientBuilder;
#Component
public class InfoFeignClient {
interface InfoCallSpec {
#RequestMapping(value = "/actuator/info", method = GET)
String info();
}
FeignClientBuilder feignClientBuilder;
public InfoFeignClient(#Autowired ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public String getInfo(String serviceId) {
InfoCallSpec spec =
this.feignClientBuilder.forType(InfoCallSpec.class, serviceId).build();
return spec.info();
}
}
That actually is possible. In Spring Cloud Zookeeper we're doing a similar thing since the name of the service in the Feign client is not the one that is there in the in Zookeeper. It can be an alias presented in the yaml file. Here you have the code example https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java#L54 and here you have the description of the dependencies feature - https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc#using-the-zookeeper-dependencies

Any samples to unit test fallback using Hystrix Spring Cloud

I wish to test the following scenarios:
Set the hystrix.command.default.execution.isolation.thread.timeoutInMillisecond value to a low value, and see how my application behaves.
Check my fallback method is called using Unit test.
Please can someone provide me with link to samples.
A real usage can be found bellow. The key to enable Hystrix in the test class are these two annotations:
#EnableCircuitBreaker
#EnableAspectJAutoProxy
class ClipboardService {
#HystrixCommand(fallbackMethod = "getNextClipboardFallback")
public Task getNextClipboard(int numberOfTasks) {
doYourExternalSystemCallHere....
}
public Task getNextClipboardFallback(int numberOfTasks) {
return null;
}
}
#RunWith(SpringRunner.class)
#EnableCircuitBreaker
#EnableAspectJAutoProxy
#TestPropertySource("classpath:test.properties")
#ContextConfiguration(classes = {ClipboardService.class})
public class ClipboardServiceIT {
private MockRestServiceServer mockServer;
#Autowired
private ClipboardService clipboardService;
#Before
public void setUp() {
this.mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testGetNextClipboardWithBadRequest() {
mockServer.expect(ExpectedCount.once(), requestTo("https://getDocument.com?task=1")).andExpect(method(HttpMethod.GET))
.andRespond(MockRestResponseCreators.withStatus(HttpStatus.BAD_REQUEST));
Task nextClipboard = clipboardService.getNextClipboard(1);
assertNull(nextClipboard); // this should be answered by your fallBack method
}
}
Fore open the circuit in your unit test case just before you call the client. Make sure fall back is called. You can have a constant returned from fallback or add some log statements.
Reset the circuit.
#Test
public void testSendOrder_openCircuit() {
String order = null;
ServiceResponse response = null;
order = loadFile("/order.json");
// use this in case of feign hystrix
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.default.circuitBreaker.forceOpen", "true");
// use this in case of just hystrix
System.setProperty("hystrix.command.default.circuitBreaker.forceOpen", "true");
response = client.sendOrder(order);
assertThat(response.getResultStatus()).isEqualTo("Fallback");
// DONT forget to reset
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.default.circuitBreaker.forceOpen", "false");
// use this in case of just hystrix
System.setProperty("hystrix.command.default.circuitBreaker.forceOpen", "false");
}

Resources