I am using Karate to do integration testing for a Spring Boot app. The app consumes two other services (Service A and Service B) . I have written Karate mocks for Service A and B. My integration tests are written as two different feature files. One of the feature is tested using Mocks for service A and B. Another feature uses Mocks for service B and Spring Contract Stubs for service A.
Feature with only mocks
Feature: Test Some feature
Background:
* configure headers = { Content-Type: 'application/json' }
* url baseUrl
#Start up the mocks
Scenario: Start the Mocks
* karate.start({ mock: '../mock/service/service-a.feature', port: 9000})
* karate.start({ mock: '../mock/service/service-b.feature', port: 9001})
Feature with mocks and Spring Contract stubs
Feature: Test Some more features
Background:
* configure headers = { Content-Type: 'application/json' }
* url baseUrl
#Start up the mocks
Scenario: Start the Mocks
# Service A will use Spring Contract stubrunner.
* karate.start({ mock: '../mock/service/service-b.feature', port: 9001})
Now, when we run the tests, the second one fails saying that the port is already used. I tried using karate.stop(9000) at the end of the feature file which is running first, but it does not help. Also, I am not sure about the behaviour of this stop method. Any suggestions to solve this problem ? Why the mock service is still running even after the tests are done ?
As far as I know, the mock should stop when the JVM exits - so I can't explain what's going on in your case. So maybe you should create a way to replicate and file a bug: https://github.com/intuit/karate/wiki/How-to-Submit-an-Issue
I personally recommend starting your mocks from the unit-test Java code (typically the JUnit class) which means you can keep a reference to the mock and then call stop() on it. Even here, normally it is not mandatory, as the mock should terminate along with the JVM. Read the docs here: https://github.com/intuit/karate/tree/develop/karate-netty#embedding
Then, note that best-practice is to dynamically provision a port and then pass that to the test or any other consumers. Especially in CI-CD pipelines, this avoids the problem of the port in use or taking too much time to de-allocate.
And maybe the direct answer you are looking for. When you call karate.start() you get an object back which is of type MockServer. So you can keep a reference to it and call stop() on it when needed: https://github.com/intuit/karate/tree/develop/karate-netty#within-a-karate-test
For example:
* def server1 = karate.start('mock1.feature')
* def port1 = server1.port
# do some tests
* server1.stop()
Related
I have looked at a few tutorials, and am not quite sure how to proceed with writing a test case for my controller method using JUnit 5. I have read up on TestRestTemplate and Mock functions, but still feel at a loss as to how to begin. I am using a MySQL database, the application contains two classes Product and Feedback, it has a OneToMany relationship, with the owning entity being Feedback.
How do I write a test for the method below?
#PostMapping("/view/{id}")
public ModelAndView addReview(#PathVariable("id") int id, #RequestParam("review") String review, HttpServletRequest request) {
Product product = dao.findById(id).get();
Feedback feedback = new Feedback(review);
product.getFeedbacks().add(feedback);
feedback.setProduct(product);
dao.save(product);
List<Feedback> fs = product.getFeedbacks();
Collections.sort(fs, new FeedbackComparator());
HttpSession session = request.getSession();
session.setAttribute("fs", fs);
return new ModelAndView("/view").addObject("product", product);
}
There are multiple ways to write a test for your Spring MVC controller endpoints:
Use #WebMvcTest and MockMvc to test only your web-layer in isolation.
For such tests, you usually mock all collaborators (in your case your dao) of your controller. Using MockMvc you can then fire requests against a mocked Servlet environment that Spring Boot creates for you. This way you can ensure e.g. your controller endpoints are properly protected (by Spring Security), your path variables and query parameters are mapped as expected, you're returning the correct HTTP response and headers, your Model contains the default attributes, etc.
You can find further information on how to write tests with MockMvc as part of this guide.
Use #SpringBootTest to populate your whole application context, start the real Servlet container (e.g. Tomcat) and invoke your endpoints with the TestRestTemplate or the WebTestClient over HTTP.
This setup requires all your external infrastructure components (database, messaging queues, external systems, etc.) to be available during test execution. This usually takes quite some time and such tests are not as fast as the first variant. Testcontainers can help you a lot here. This way you can ensure the whole use case is working as expected and write integration tests for your application.
So as a general recommendation you should have both kinds of tests and at least ensure your important happy-paths are working as expected with an integration test. For more low-level checks for your controller, the #WebMvcTest annotation comes quite handy.
I have recently started out with Spring and am unsure about how to approach this issue. I have a Spring boot program which makes calls to remote REST APIs. For example an AddressService class with getAddress(String user) method, which makes a HTTP call and returns a JSON response. I would like to set up Spring profiles for development purposes local, dev, uat, prod.
When the program is running with the local profile, I would like to "mock" these external API calls with an expected JSON response, so I can just test logic, but when it is run in any of the other profiles I would like to make the actual calls. How can I go about doing this? From what I read, there's many ways people approach this, using WireMock, RestTemplate, Mockito etc. I'm confused about which is the way to go.
Any advice would be greatly appreciated. Thanks.
WireMock,Mockit is for unittest, to mock the real request. Example here:
How do I mock a REST template exchange?
When you need a running implementation with a mock, i think the easiest way is that you have a interface
public interface AdressAdapter {
public List<Adress> getAddress(String name);
}
And two different implementations depending on the profile.
#Profile("local")
public class DummyAdress implements AdressAdapter{
#Override
public List<Adress> getAddress(String name) {
//Mock here something
return null;
}
}
! means NOT locale profile in this case.
#Profile("!local")
public class RealAdress implements AdressAdapter{
#Override
public List<Adress> getAddress(String name) {
//Make Restcall
return null;
}
}
What you could do is use different application.properties files depending on your profile. That way, you just change the url to a mock server for your local profile.
So what you have to do is :
Create another application.properties in your resources folder named : application-local.properties.
Change the url of the desired service.
Start your application with the VM option -Dspring.profiles.active=local.
Here is a link that describe well what you want to achieve.
For your mock server, you could use Wiremock, Mountebank, Postman,... that can be start separately and mock specific endpoints to return what you want.
I am confused about how an infinite loop of feign calls might behave.
An example:
Assume I have 2 APIs, A & B.
if I call API A, which in turn calls API B via a feign HTTP call, which in turn calls API A again via feign, will it recognize this and break the call chain?
Quick flowchart of calls:
A -> B -> A -> B ... Repeat infinitely?
I have not tried this code, it is just an idea。
But I am assuming that spring-cloud-starter-feign will provide some methods to resolve this problem? Is this assumption correct?
#PostMapping(RestJsonPath.API_A)
ResponseEntity<byte[]> apiA();
#PostMapping(RestJsonPath.API_B)
ResponseEntity<byte[]> apiB();
Will it execute until it times out or hystrix will stop it?
TL;DR:
Feign will keep the connection open on the initial request from A to B until the pre-configured timeout kicks in. At this point, Feign will time out the request and if you have specified a Hystrix fallback, Spring will use your Hystrix fallback as the response.
Explanation:
spring-boot-starter-feign provides an abstraction layer for writing the HTTP request code. It will not handle potential loops or cycles in your code.
Here is an example spring boot feign client from their tutorials website for demonstration:
#FeignClient(value = "jplaceholder",
url = "https://jsonplaceholder.typicode.com/",
configuration = ClientConfiguration.class,
fallback = JSONPlaceHolderFallback.class)
public interface JSONPlaceHolderClient {
#RequestMapping(method = RequestMethod.GET, value = "/posts")
List<Post> getPosts();
#RequestMapping(method = RequestMethod.GET, value = "/posts/{postId}", produces = "application/json")
Post getPostById(#PathVariable("postId") Long postId);
}
Notice first that this is an interface - all the code is auto generated by Spring at startup time, and that code will make RESTful requests to the urls configured via the annotations. For instance, the 2nd request allows us to pass in a path variable, which Spring will ensure makes it on the URL path of the outbound request.
The important thing to stress here is that this interface is only responsible for the HTTP calls, not any potential loops. Logic using this interface (which I can inject to any other Spring Bean as I would any other Spring Bean), is up to you the developer.
Github repo where this example came from.
Spring Boot Docs on spring-boot-starter-openfeign.
Hope this helps you understand the purpose of the openfeign project, and helps you understand that it's up to you to deal with cycles and infinite loops in your application code.
As for Hystrix, that framework comes in to play (if it is enabled) only if one of these generated HTTP requests fails, whether it's a timeout, 4xx error, 5xx error, or a response deserialization error. You configure Hystrix, as a sensible default or fallback for when the HTTP request fails.
This is a decent tutorial on Hystrix.
Some points to call out is that a Hystrix fallback must implement your Feign client interface, and you must specify this class as your Hysterix fallback in the #FeignClient annotation. Spring and Hystrix will call your Hystrix class automatically if a Feign request fails.
I have a Jersey client which performs post request to some black box service.
Also I have POJO mapping feature enabled.
I have integration tests already, they are calling real black box service.
Now I need to test my application without calling real black box service.
My question is: how can I test this Jersey client? I mean: how can I test Jersey client without calling real black box service?Maybe there is some possibility to mock JSON response in tests?
Environment: jersey-client and jersey-json versions - 1.19.1.
You can log the request using LoggingFilter to make sure your requests are according to your estimate. For running your test, you can use http://mockable.io/.
P.S. Do not forget to replace the Webtarget URL with MockableIO's URL.
You can try Karate which has recently introduced a way to create simple mocks for JSON responses. Here is an example:
https://gist.github.com/ptrthomas/35ef9d40623cbeade7388b2cbb29a3b1
While the above example is a "smart" mock, it is very easy to create hard-coded mocks, here is an example: https://github.com/intuit/karate/blob/master/karate-netty/src/test/java/com/intuit/karate/mock/_mock.feature
You can easily read files from JSON by using the read keyword.
I have performed an investigation concerning my question. Mentioned investigation resulted in such facts:
I haven't found features/mechanisms/etc. to test Jersey client without real calls to the server (in my case - black box service).
Jersey test framework provides features for testing Jersey server, but there are no features for testing Jersey client
The only one solution for testing client without server is to refactor my code in following way: split logic into two phazes. First phaze: get JSON response using Jersey. Second phaze: get JSON response (which we get in first phaze) and transform it into desired object. In general you'll have next code as a result:
Class< String > jsonResposeClass = String.class;
String jsonResponse = post( yourRequest, jsonResposeClass );
ObjectMapper mapper = new ObjectMapper();
YourResponseBean bean = mapper.readValue( jsonResponse , responseClass );
The solution above will give you a possibility to use mocking libraries like Mockito and mock method which performs post request.
If you have found any other solution/feature/special mechanism/etc which can also help in such kind of situation - please share :)
I have the below route. In unit test, since I doesn't have the FTP server available, I'd like to use camel's test support and send an invalid message to "ftp://hostname/input" and verify that it failed and routed to "ftp://hostname/error".
I gone through the documentation which mainly talks about using the "mock:" endpoint but I am not sure how to use it in this scenario.
public class MyRoute extends RouteBuilder
{
#Override
public void configure()
{
onException(EdiOrderParsingException.class).handled(true).to("ftp://hostname/error");
from("ftp://hostname/input")
.bean(new OrderEdiTocXml())
.convertBodyTo(String.class)
.convertBodyTo(Document.class)
.choice()
.when(xpath("/cXML/Response/Status/#text='OK'"))
.to("ftp://hostname/valid").otherwise()
.to("ftp://hostname/invalid");
}
}
As Ben says you can either setup a FTP server and use the real components. The FTP server can be embedded, or you can setup a FTP server in-house. The latter is more like an integration testing, where you may have a dedicated test environment.
Camel is very flexible in its test kit, and if you want to build an unit test that do not use the real FTP component, then you can replace that before the test. For example in your example you can replace the input endpoint of a route to a direct endpoint to make it easier to send a message to the route. Then you can use an interceptor to intercept the sending to the ftp endpoints, and detour the message.
The advice with part of the test kit offers these capabilities: http://camel.apache.org/advicewith.html. And is also discussed in chapter 6 of the Camel in action book, such as section 6.3, that talks about simulating errors.
In your example you could do something a like
public void testSendError() throws Exception {
// first advice the route to replace the input, and catch sending to FTP servers
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:input");
// intercept valid messages
interceptSendToEndpoint("ftp://hostname/valid")
.skipSendToOriginalEndpoint()
.to("mock:valid");
// intercept invalid messages
interceptSendToEndpoint("ftp://hostname/invalid")
.skipSendToOriginalEndpoint()
.to("mock:invalid");
}
});
// we must manually start when we are done with all the advice with
context.start();
// setup expectations on the mocks
getMockEndpoint("mock:invalid").expectedMessageCount(1);
getMockEndpoint("mock:valid").expectedMessageCount(0);
// send the invalid message to the route
template.sendBody("direct:input", "Some invalid content here");
// assert that the test was okay
assertMockEndpointsSatisfied();
}
From Camel 2.10 onwards we will make the intercept and mock a bit easier when using advice with. As well we are introducing a stub component. http://camel.apache.org/stub
Have a look at MockFtPServer!
<dependency>
<groupId>org.mockftpserver</groupId>
<artifactId>MockFtpServer</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
With this one you can simulate all sorts of behaviors like permission problems, etc:
Example:
fakeFtpServer = new FakeFtpServer();
fakeFtpServer.setServerControlPort(FTPPORT);
FileSystem fileSystem = new UnixFakeFileSystem();
fileSystem.add(new DirectoryEntry(FTPDIRECTORY));
fakeFtpServer.setFileSystem(fileSystem);
fakeFtpServer.addUserAccount(new UserAccount(USERNAME, PASSWORD, FTPDIRECTORY));
...
assertTrue("Expected file to be transferred", fakeFtpServer.getFileSystem().exists(FTPDIRECTORY + "/" + FILENAME));
take a look at this unit test and those in the same directory...they'll show you how to standup a local FTP server for testing and how to use CamelTestSupport to validate scenarios against it, etc...
example unit test...
https://svn.apache.org/repos/asf/camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FromFileToFtpTest.java
which extends this test support class...
https://svn.apache.org/repos/asf/camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpsServerTestSupport.java
In our project we do not create mock FTP Server to test the route but we use properties that can be replaced by a file Camel Component for the local development and unit testing.
Your code would look like this:
public class MyRoute extends RouteBuilder
{
#Override
public void configure()
{
onException(EdiOrderParsingException.class)
.handled(true)
.to("{{myroute.error}}");
from("{{myroute.input.endpoint}}")
.bean(new OrderEdiTocXml())
.convertBodyTo(String.class)
.convertBodyTo(Document.class)
.choice()
.when(xpath("/cXML/Response/Status/#text='OK'"))
.to("{{myroute.valid.endpoint}}}")
.otherwise()
.to("{{myroute.invalid.endpoint}}");
}
}
And locally and for system test we use a file endpoint declared in the property file:
myroute.input.endpoint=file:/home/user/myproject/input
myroute.valid.endpoint=file:/home/user/myproject/valid
myroute.invalid.endpoint=file:/home/user/myproject/invalid
myroute.error=file:/home/user/myproject/error
or in a JUnit CamelTestSupport you can use the useOverridePropertiesWithPropertiesComponent method to set the properties you want to overrides.
As an alternative you can also use a "direct" route instead but you can miss some File options that can be tested by the unit test.
And we only test the FTP connection with the real system by setting the properties like this:
myroute.input.endpoint=ftp://hostname/input
myroute.valid.endpoint=ftp://hostname/valid
myroute.invalid.endpoint=ftp://hostname/invalid
myroute.error=ftp://hostname/error
With this you can also have different configuration for e.g production server that will differentiate from the Integration Test Environment.
Example of Properties for Production environment:
myroute.input.endpoint=ftp://hostname-prod/input
myroute.valid.endpoint=ftp://hostname-prod/valid
myroute.invalid.endpoint=ftp://hostname-prod/invalid
myroute.error=ftp://hostname-prod/error
In my opinion it is totally acceptable to use file endpoint to simplify the JUnit code and it will test the route only and not the connection.
Testing the connection is more like an Integration Test and should be executed on the real server connected with the real external system (in your case FTP servers, but can be other endpoints/systems as well).
By using properties you can also configure different URL's per environment (For example: we have 3 testing environments and one production environment, all with different endpoints).