Trying to figure out how to best unit test an http:outbound-gateway in a Spring Integration workflow.
Here's what our gateway looks like:
<int-http:outbound-gateway id="gateway"
request-channel="registrationQueue"
message-converters="jsonMessageConverter"
url-expression="#urlGenerator.resolve()"
http-method="POST"
expected-response-type="javax.ws.rs.core.Response"
reply-channel="nullChannel"
error-handler="httpResponseErrorHandler"/>
Specifically, we want to..
Assert serialization of the objects being sent; do the message-converters correctly process messages coming from the request-channel?
Verify response handling from the 3rd party service; what is the behavior given various responses (expected & unexpected) and errors (internal & external)?
We've got a number of unit tests that mock out the end points and assert the steps of our integration workflow behave as expected. Something like the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:test-config.xml"})
public class FileRegistrationWorkflowTest {
...
#Autowired
private MessageChannel fileFoundChannel;
#Autowired
private QueueChannel testRegistrationQueue;
...
#Test
public void shouldQueueRegistrationForFileWithEntityId() {
// Given
mockFileLookupService(FILE_ID, FILENAME_WITH_ENTITY_ID);
// When
fileFoundChannel.send(MessageBuilder.withPayload(FILE_ID).build());
// Then
Message<?> message = testRegistrationQueue.receive();
assertThat(message, hasPayload(expected));
}
}
This method of testing works great for the steps along the workflow. Our trouble is testing the the end point gateways..
We can't mock the http:outbound-gateway, then we aren't testing it.
We don't want to deploy a real HTTP service to interact with, that's more an integration test.
The 3rd party service is only resolved by the url-expression, so there isn't a Spring bean to mock out.
Perhaps we can intercept the HTTP request Spring tries to send?
In the framework tests we use a DirectFieldAccessor to replace the endpoint's RestTemplate with a mock (actually a stub). However, this doesn't test the converters.
You can get even more sophisticated, where the real RestTemplate can be tested; just get a reference to it (with the SI TestUtils.getPropertyValue() or a DirectFieldAccessor) and configure it as discussed in the Spring Framework documentation.
You can get a reference to the handler with bean name endpointId.handler.
Related
I have a hypothetical rest end point.
#GetMapping(value = "/happy/{happyId}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Response<?> getHappy(#PathVariable Long happyId) {
Response response = new Response();
response.update(happyService.getById(happyId));
return response;
}
In this code, happyService could throw UnhappyException if id does not exist, and this code is tested in another place, eg) HappyServiceTest.
Now, let say if I want to test my rest controller, should I also be testing the exception flow? Or is this unnecessary?
eg)
HappyRestControlerTest.java
#Test
void testUnHappy() {
...
assertThrows(UnhappyException.class () -> {happyService.getById(-1L)});
}
Is this unnecessary test since I tested the behaviour of happyService in HappyServiceTest?
Considering your example is a unit test, the real HappyService should not even be in this equation and should be mocked. So if you were writing a test for the controller facade, you'd essentially be testing that it
A. Calls HappyService, given ID X, Y is returned
B. Calls HappyService, given ID X, exception is thrown and propagated.
However a unit test is quite pointless for controllers whose only job is to forward a call to a service. There is a better way to test controllers where a web server is brought up and the test is performed against your Spring context to the extent that you choose, allowing you to test the controller by making HTTP requests to it and checking its output that way. This approach is outlined in Testing the Web Layer.
For this layer of application there is a specific type of testing. It is called MVC testing and for this you mock your service with some specific response for some specific input and you verify with a test that the Controller behaves as expected.
See the following example
#SpringBootTest
#AutoConfigureMockMvc
public class TestingWebApplicationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HappyService service;
#Test
public void shouldReturnMessage() throws Exception {
when(service.getById(1)).thenReturn("Happy Response 1");
this.mockMvc.perform(get("/happy/1"))
.andExpect(status().isOk())
.andExpect(content()
.string(containsString("Happy Response 1")));
}
}
This is a type of test that simulates, what the client will receive from controller http status code, http headers, http body content etc...
Spring Boot already includes support for this MockMvc testing via the dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
There are different types of testing. You are talking about unit testing. In Unit testing you need to strive to break the code to the smallest logical blocks (units) and test them each with a separate unit test. As you mentioned, you already tested internal logic of your controller elsewhere. And that is correct. Testing a controller itself in unit testing doesn't make much sense at all. So, the answer to your question - no controller itself should not be tested in Unit Testing. There other types of tests such as load (stress) test, Functional tests etc. Controller should be tested in Functional testing where you test the entire logical path rather then the smallest logical unit. I won't get into details how it is done here, but controller basic functionality test is part of functional test and not unit test.
I'm trying to learn some new things using Quarkus, and I kinda got stuck on something:
I want to create some tests that are self sustained, I mean, I should be able to run each one independently of each other.
I'm using Testcontainers(PostgreSQL) to run some component tests, but I would like to each #Test be able to run on a 'clean database', but I don't think that stoping the container and starting it once again for each #Test would be a good idea. I could have a #BeforeEach(container.dropColumn()) or #BeforeEach(container.truncateColumn()) but: 1 - I don't know if this is the better way, 2 - I don't know how to do this.
Given an hypothetical test scenario:
Scenario 1 - Pass
Register new User
Find User by its id
assert(one_user_on_db)
Scenario 2 - Fail
Try to Register new User with invalid data
Find all users
assert(zero_users_on_db)
The second test fail because the data from the first scenario is still on the database.
Here, some code to help.
resources
#Inject
UserService userService;
#POST
#Transactional
public Response saveUser(#Valid UserDto user){
return Response.status(Response.Status.CREATED)
.entity(userService.saveUser(user, user.getPassword())).build();
}
#GET
public Iterable<User> findAll(){
return userService.findAll();
}
tests
#Test
void shouldSuccessfullyCreateUser(){
User mockUser = MockUser.onlyMandatoryFields() //MockUser
given()
.body(mockUser)
.contentType(ContentType.JSON)
.when()
.post("/users").prettyPeek()
.then()
.statusCode(Response.Status.CREATED.getStatusCode());
#Test
void shouldHaveAnEmptyDatabase(){
User[] userList = given()
.contentType(ContentType.JSON)
.when()
.get("/users").prettyPeek()
.then()
.extract()
.response()
.as(User[].class);
assertEquals(0, userList.length);
}
I've already tried #TestTransaction as described in Quarkus Docs but no success.
I was looking for something like #DirtiesContext from Spring.
Anyway, I've got an open repository, if you want to look further into the code.
The tests can be found here.
#TestTransaction works properly when you test repositories or CDI beans directly.
I've already tried #TestTransaction as described in Quarkus Docs, but no success.
It works if tests run in the same transaction context as the tested code.
Your REST resource works under a different transaction context than your test method; therefore, #TestTransaction will not work in your case. In your case the transaction gets committed at the end of the rest call; hence you cannot rollback it.
See an example of a working test validating the repository directly.
#Test
#TestTransaction
void shouldFail_whenCreatingNewLedgerWithUnrecognizedType() {
//when/then
assertThatThrownBy(() -> customSqlRepository.insertWithUnrecognizedType())
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Check constraint violation")
.hasMessageContaining("LEDGER_ACCOUNT_TYPE IN(");
}
A similar effect to the Spring DirtiestContext could be achieved using Quarkus #QuarkusTestProfile, described as:
If a test has a different profile to the previously run test, then
Quarkus will be shut down and started with the new profile before
running the tests. This is obviously a bit slower, as it adds a
shutdown/startup cycle to the test time but gives a great deal of
flexibility.
I think it might work for you but will be slow.
Consider other solutions
Write an integration test annotated by #TestTransaction validating a service class plus write a test validating your REST controller with the mock of the service injected
Truncate the database before each test,
Create a REST call that will delete the created user and call it from your tests,
I am implementing a message translator pattern with Apache Camel, to consume messages from a RESTful endpoint and send them onward to an AMQP endpoint.
The enclosing application is based on Spring Boot, and so I'm using Camel's "spring-boot" component to integrate the two frameworks. As suggested by the documentation in this spring-boot link, I'm implementing my Camel route inside of a #Configuration-annotated class which extends RouteBuilder:
#Component
public class MyRestToAmqpRouter extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jetty:http://my-restful-url")
.process(exchange -> {
// convert the message body from JSON to XML, take some
// incoming header values and put them in the outgoing
// body, etc...
}).to("rabbitmq://my-rabbitmq-url");
}
}
My question involves how to go about unit-testing this translation, without needing an actual RESTful endpoint or configured RabbitMQ broker? I've read many online examples, as well as the Camel in Action book... and it seems like the typical approach for unit testing a Camel route is to cut-n-paste the route into your unit test, and replace one or more endpoint URL's with "mock:whatever".
I guess that sorta works... but it's awfully brittle, and your test suite won't recognize when someone later changes the real code without updating the unit test.
I've tried to adapt some Spring-based unit testing examples with mocks, like this:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Application.class})
public class MyRestToAmqpRouterTest extends AbstractJUnit4SpringContextTests {
#Produce(uri = "jetty:http://my-restful-url")
private ProducerTemplate fakeRest;
#EndpointInject(uri = "rabbitmq://my-rabbit-url")
private MockEndpoint fakeRabbit;
#Test
#DirtiesContext
public void testRouter() throws InterruptedException {
fakeRabbit.expectedMessageCount(1);
fakeRest.sendBodyAndHeader("", "header-1", "some value");
fakeRabbit.assertIsSatisfied();
}
}
My hope was that Camel would take those endpoint URLs from the unit test, register them as mocks... and then use the mocks rather than the real endpoint when the real code tries to use those URLs.
However, I'm not sure that this is possible. When I use the real URLs in the unit test I get IllegalArgumentException's, because you apparently can't inject a "real" endpoint URL into a MockEndpoint instance (only URLs prefixed with "mock:").
When I do use a "mock:..." endpoint URL in my unit test, then it's useless because there's nothing tying it to the real endpoint URL in the class under test. So that real endpoint URL is never overridden. When the real code is executed, it just uses the real endpoint as normal (and the goal is to be able to test without an external dependency on RabbitMQ).
Am I missing something on a really fundamental level here? It seems like there would be a way for unit tests to inject fake routes into a class like this, so that the code under test could switch from real endpoints to mock ones without even realizing it. Alternatively, I suppose that I could refactor my code so that the anonymous Processor were elevated to a standalone class... and then I could unit test its translation logic independently of the route. But that just seems like an incomplete test.
Some pointers what you may do.
You can read the Camel book again about testing, and pay attention to using advice with
http://camel.apache.org/advicewith.html.
And there is also mockEndpointsAndSkip
http://camel.apache.org/mock.html
And you can also use the stub component
http://camel.apache.org/stub
Or use property placeholders in your routes, and then configure the uris to be mock/stub etc for testing, and use the real ones for production
http://camel.apache.org/using-propertyplaceholder.html
I wrote code which calls the Jersey client API which in turn calls a web service which is out of my control. I do not want my unit test to call the actual web service.
What is the best approach for writing a unit test for code which calls the Jersey client API? Should I use the Jersey server API to write a JAX-RS web service and then use the Jersey Test Framework for the unit test? Or should I mock out the Jersey web service calls? I have access to JMock. Or should I try another approach?
During my research, I found this discussion describing various options, but I did find a complete solution. Are there any code examples available showing a suggested JUnit approach? I could not find any in the Jersey documentation.
Here is the relevant source code:
public String getResult(URI uri) throws Exception {
// error handling code removed for clarity
ClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create(clientConfig);
WebResource service = client.resource(uri);
String result = service.accept(accept).get(String.class);
return result;
}
Here are examples of test code I would like to pass. I would like to test (1) passing in a valid URI and getting a valid string back and (2) passing in an invalid (for whatever reason -- unreachable or unauthorized) URI and getting an exception back.
#Test
public void testGetResult_ValidUri() throws Exception {
String xml = retriever.getResult(VALID_URI);
Assert.assertFalse(StringUtils.isBlank(xml));
}
#Test(expected = IllegalArgumentException.class)
public void testGetResult_InvalidUri() throws Exception {
retriever.getResult(INVALID_URI);
}
Everything above is the simple description of what my code does. In reality, there is a layer on top of that that accepts two URIs, first tries calling the first URI, and if that URI fails then it tries calling the second URI. I would like to have unit tests covering (1) the first URI succeeds, (2) the first URI fails and the second URI succeeds, and (3) both URIs fail. This code is sufficiently complex that I want to test these different scenarios using JUnit, but to do this I either need to run actual stand-in web services or mock out the Jersey client API calls.
Try to use Mockito or Easymock for mocking service calls. You need to mock only these methods which are actually used - no need to mock every method. You can creat mock object for WebResource class, then mock accept method call.
In #BeforeClass/#Before JUnit test method write something like (Mockito example)
WebResource res = mock(WebResource.class);
when(res.accept(something)).thenReturn(thatWhatYouWant);
Then in your tests you can use res object as if it was real object and call mock method on it. Instead of returning value you can also throw exceptions. Mockito is pretty cool.
Typically what you are really after is "does the way I use the Jersey Client DSL produce a request to the correct URL with the correct payload and URL parameters". Testing this with Mockito is really verbose and the setup code will usually end up looking something like this:
when(authentication.queryParam(eq("sa"), anyBoolean())).thenReturn(testAuthentication);
when(testAuthentication.resolveTemplate("channel", "smf")).thenReturn(testAuthentication);
when(testAuthentication.request(
MediaType.APPLICATION_JSON_TYPE)).thenReturn(mockRequestBuilder);
when(mockRequestBuilder.post(any(Entity.class))).thenReturn(mockResponse);
when(mockResponse.readEntity(ResponseWrapper.class)).thenReturn(successfulAuthResponse());
And this is basically just for a single REST request. It's overly verbose, and instead of testing the hoped outcome you are just replicating the steps you think are correct in using the Jersey Client DSL.
Instead of the above, I would aim for mocking a simple service. For this I've used WireMock which starts a Jetty server and where I can stub things like "expect a request to this URL, respond with this message and verify that the payload is this".
I know this is edging on an integration test and it is a bit slower than just using Mockito but I value testing the real outcome and I value the readability of the tests way more in this case.
Setup for a WireMock based Jersey Client test looks something like this:
#Test
public void exactUrlOnly() {
stubFor(get(urlEqualTo("/some/thing"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello world!")));
assertThat(testClient.get("/some/thing").statusCode(), is(200));
assertThat(testClient.get("/some/thing/else").statusCode(), is(404));
}
Just implement a work-alike service and in your unit test setup start the service using HttpServerFactory.
In Spring MVC 3.0, how do I test that a particular method gets invoked?
For example, I have this controller method:
public class myController {
#RequestMapping(value = "/create", method = RequestMethod.GET)
public ModelAndView create(ModelMap map) {
map.put("category", new Category());
return new ModelAndView("views/someView", map);
}
}
How do I test that this create() method gets called when somebody requests http://example.com/create url.
In Unit Tests, you should only test your controller's Java code, without using any Servlet technology.
In integration tests you can do one of several things:
Use the org.springframework.mock.web package in the spring-test artifact, which contains Mock Objects for request, response, servletContext to fire fake requests at your controllers and read the data from the fake responses.
Or use a web testing framework like Selenium that works against a deployed webapp.
How do I test that this create() method gets called when somebody requests http://example.com/create url.
Looks really like a integration test. Sean Patrick Floyd already mentioned some ways how to test that, but to my understanding none of this options really tests that a request to an url really invokes the method. -- The mocking way simulates the request and the selenium test tests only the return value, but not the Invocation. -- Don't get my wrong, I believe that this two other tests are in most cases better (easyer to test and even more valid test results), but if you really want to test the invokation I would come up with this solution.
Use a web testing framework like Selenium (or Selenium 2/Webdriver) or only a simple one that only generates HTTP requests. -- To do that tests you will need of curse the deployed application. -- so it is realy an integration test.
To check that the method is invoked. I would recommend to use a logging tool (Log4J). Then use a Tool like AspectJ or Spring AOP Support to add logging statements to your controller method. The logging should be written to some logger that writes to an other destination then the other loggers you use.
At the end the last step is, that you need to verify that the expected logging statement is is the logfile after the test sends the http request. (Pay attention to the fact, that the logging may is asynchronous.)