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,
Related
I'm currently learning unit tests and integration tests. In my unit tests I have been using Mockito for mocking, such as when(something).thenReturn(something) etc. I used this approach to test the business logic of the application.
Now I am trying to create integration tests to test all the endpoints of my application. The layers of my application are as follows: Controller -> Interface -> Service -> Repository.
When I tried to create an integration test for the POST method that creates a user, the request should have returned the created user, but returned an empty string. The test looked like this:
#Test
void create() throws Exception {
UserCreationDTO user = new UserCreationDTO(1L, "Steve", "steve#mail.com", "password");
ObjectMapper objectMapper = new ObjectMapper();
String text = objectMapper.writeValueAsString(user);
this.mockMvc
.perform(
MockMvcRequestBuilders.post("/users").contentType("application/json").content(text))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Steve"))
.andExpect(MockMvcResultMatchers.jsonPath("$.email").value("steve#mail.com"))
.andExpect(MockMvcResultMatchers.jsonPath("$.password").value("password"));
}
I later found out that I need to add a line of code to mock the userInterface and what it should return.
Mockito.when(userInterface.create(Mockito.any())).thenReturn(user);
After adding it, the method started returning the created user and the test passed. After reading a couple of articles, I came across an approach that says an integration test should not contain mocking.
So my question is: Is this approach I have taken correct and can it be considered a correct integration test or do I need to get rid of the mocking of userInterface? In case I have to remove the mocking, how can I ensure that the test passes and returns not just an empty string, but an actually created user?
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 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
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.
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.)