How to run a integration test in Spring with #SpringBootTest - java

I am trying to learn integration tests with Spring. So I am following this tutorial:
http://www.lucassaldanha.com/unit-and-integration-tests-in-spring-boot/
I am fase a test Class like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class GreetingControllerTest {
#Test
public void helloTest(){
TestRestTemplate restTemplate = new TestRestTemplate();
Hello hello = restTemplate.getForObject("http://localhost:8080/hello", Hello.class);
Assert.assertEquals(hello.getMessage(), "ola!");
}
}
But when I mvn install, I get this error:
I/O error on GET request for "http://localhost:8080/hello": Connection refused; nested exception is java.net.ConnectException: Connection refused
So... What am I doing wrong? What I need to do to make my test work?
Note: If I run mvn spring-boot:run the project works fine and I request the end point using any browser.

That's because of the following property in your test class:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
As per spring documentation, it binds the application to a random port. So, while sending the request, there's a chance that the app won't be running on port 8080 and hence, you get connection refused error.
If you want to run the app on a particular port, you need to remove webEnvironment property and annotate your class with the following:
#IntegrationTest("server.port=8080")
Another approach is to get the port and add it into the url, below is the snippet to get the port:
#Autowired
Environment environment;
String port = environment.getProperty("local.server.port");

You can autowire the random port value to a field in the test class if you want to:
#LocalServerPort
int port;
but you can autowire the restTemplate and you should be able to use it with relative URI without the need to know the port number:
#Autowired
private TestRestTemplate restTemplate;
#Test
public void helloTest(){
Hello hello = restTemplate.getForObject("/hello", Hello.class);
Assert.assertEquals(hello.getMessage(), "ola!");
}

Related

How can I run a REST Controller test without loading data.sql?

I'm developing a Spring Boot application and I'm having some trouble setting up RESTController tests. The problem is that, when I run the test classes individually, they all work. However, when I try to run all the test classes at once, only the first one works and the others throw java.lang.IllegalStateException: Failed to load ApplicationContext. I have been debugging this and the problematic line is the following:
INSERT INTO users(username,password, email) VALUES ('admin','$2a$10$bicbzJTFskk8.sHWJauxCu2RzDIqXk/zCxQDZ5ByLQw0m0lQ6l2Pa', 'admin#mail.com') [23505-200] Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:spring-boot/target/classes/db/hsqldb/data.sql]: INSERT INTO users(username,password, email) VALUES ('admin','$2a$10$bicbzJTFskk8.sHWJauxCu2RzDIqXk/zCxQDZ5ByLQw0m0lQ6l2Pa', 'admin#mail.com'); nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_4D ON PUBLIC.USERS(USERNAME) VALUES 1"; SQL statement:
This leads me to think that the data.sql script is being executed on every test (which I believe should not be the case since my controller tests shouldn't rely on DB data). On top of that, the data is not being flushed after executing each class, so the first one works fine and the rest throw a #Unique exception because the data is already there.
My REST controller tests look like this:
#DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc(addFilters = false)
public class GameControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
GameService gameService;
#BeforeEach
void setup() {
Game game1 = new Game();
game1.setId(1);
Game game2 = new Game();
game2.setId(2);
Lobby lobby = new Lobby();
when(gameService.findAll()).thenReturn(List.of(game1, game2));
when(gameService.createFromLobby(lobby)).thenReturn(game1);
when(gameService.gameCount()).thenReturn(List.of(game1, game2).size());
when(gameService.findGameById(1)).thenReturn(game1);
}
#Test
void testGetAllGames() throws Exception {
mockMvc.perform(get("/games")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[1].id", is(2)));
}
As you can see, I also tried to use #DirtiesContext(classMode = ClassMode.BEFORE_CLASS) but it does not fix the problem.
I feel like there are two different questions in this one.
First your problem is, that your entire SpringBootApplication is started because you are using #SpringBootTest use #WebMvcTest instead. You also "need" to use
#LocalServerPort
private int port;
to actually access the port you add with (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Your second problem
On top of that, the data is not being flushed after executing each class, so the first one works fine and the rest throw a #Unique exception because the data is already there.
Can easily be fixed by annotating your test with #Transactional tho I don't believe this to be the solution for your actual problem.
Thanks both for your answers. I had to do the following changes:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = GameController.class)
public class GameControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
private GameService gameService;
// I also have to mock all the services called from gameService:
#MockBean
private UserService userService;
#MockBean
private PlayerService playerService;
#MockBean
private DataSource dataSource;
// (...)
I didn't know that I also had to mock the services that were not called directly by the service. This way, #WebMvcTest works as expected.

Spring boot test without database connection

At first I had the following annotation above my test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
With that configuration it tries to connect to my database, which will give me this error if my database is not running:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
I would like my test to run without any connection to a database, which is why I tried to change the annotations, so my test class now looks like this:
#RunWith(SpringRunner.class)
#DataJpaTest
#WebMvcTest(CitizenController.class)
#AutoConfigureMockMvc
public class CitizenControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CitizenRepository citizenRepository;
#MockBean
private WeeklyCareRepository weeklyCareRepository;
#MockBean
private SubCategoryCareRepository subCategoryCareRepository;
#Autowired
private ObjectMapper objectMapper;
private static List<Citizen> mockCitizenList;
private String citizenJson;
However, I am now getting another error:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [controllers.CitizenControllerTest]: [#org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.context.SpringBootTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper)]
Is it possible to run my test without a database connection? If so, what am I doing wrong/missing?
You can just mock the method that will connect to database in your repository class in the #Test method.
#SpringBootTest
#AutoConfigureMockMvc
class StoreApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private CitizenRepository citizenRepository;
#Test
void contextLoads() {
}
#Test
public void test() {
Mockito.when(citizenRepository.getDataFromDB()).thenReturn("Something you'd like to Return");
}
}
After doing that, citizenRepository.getDataFromDB() will not connect to database when it's called.
Update After Your Comment:
Then you can just create "src/test/resources" and copy your application.properties or application.yml from "src/main/resources" to that directory and comment the mysql connection part.
If you don't have "src/test/resources/application.properties", then spring will read "src/main/resources/application.properties" by default and configure the project according to that file, since you have datasource configuration in it, spring will try to connect to the database, if database server is down, you would get the failure.

Connection refused when using wiremock

I have this piece of code in a Junit, where I clearly set the port to 8888
when(clientUtils.getLinkUrl(eq(HOSTELS_MICROSERVICE.name()), eq(HOSTELS_MICROSERVICE.name()), anyMap()))
.thenReturn("http://localhost:8888/HOSTELS/HOSTELSMethods");
stubFor(com.github.tomakehurst.wiremock.client.WireMock.get("/HOSTELS/HOSTELS_LIST").willReturn(
aResponse().withStatus(200)
.withHeader("Content-Type", APPLICATION_JSON_VALUE)
.withBody(ResourceUtils.getResourceFileAsString ("__files/HOSTELS.json"))));
but when I run the test I got this error on this line:
stubFor(com.github.tomakehurst.wiremock.client.WireMock.get("/HOSTELS/HOSTELS_LIST").willReturn(..
and the error:
wiremock.org.apache.http.conn.HttpHostConnectException: Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
For Java users
Based on the WireMock docs.
There are 3 possibilities to use WireMock in your tests :
If you are using Wiremock as JUnit 4 rule to configure the port use :
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
...
#Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8888));
If you are using new instance and start it from your Test class (for example #Before) :
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
...
public class Test {
WireMockServer wm;
#BeforeEach
void setUp() {
wm = new WireMockServer(options().port(8888));
wm.start();
}
#Test
void test() {
wm.stubFor(...);
}
}
With static configuration of default instance (not using new instance in your test) :
WireMock.configureFor(8888);
For Kotlin users
If you are using kotlin you can add actual wiremock instance to stubFor and verify calls like wm.stubFor() and configure the port like in option 3 of this answer.
Posting my previous comment as an answer as it seems to have helped a few people. Thanks #jmrah. :)
For Kotlin and JUnit5, this can be resolved by adding the actual WireMockServer instance to the stubFor or verify method calls.
wireMockServer.stubFor()
or
wireMockServer.verify()
After adding this, the tests should work.

How to get the running server port in a SpringBoot test?

I'm creating some unit tests for a spring boot application with an Apache Camel route, using Spock as testing framework, and I need to mock a response from another application. I made a mock controller for that, but i need to inject the port that the test is running in to a property. Is there a way to get the port that the test is running on?
I tried with
#LocalServerPort
private int port
and with
#Autowired Environment environment;
String port = environment.getProperty("local.server.port");
but both return a -1, I donĀ“t know any other ways to get the port
My test is configured with the following annotations:
#RunWith(SpringRunner)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles('test')
Also, is there a way to inject that random port in the application-test.yml file? Ideally I would need to do something like this in my application-test.yml file:
app:
service: localhost:${server.port}
Where the port is the random port that the test is running on.
Could you try this :
#SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
public class test{
#LocalServerPort
private int rdmServerPort;
#LocalManagementPort
private int rdmManagementPort;
...
}

Hoverfly simulationMode conflicts with Spring Cloud Config Server in unit test

I'm using hoverfly in my spring boot project's unit test.
The background
The spring boot project will grab its config (connection timeout etc.) from spring cloud config server.
To test whether my timeout configs work, I write a unit test, and expect the hoverfly can return with a long delay , then my customized restTemplate can throw timeout error instead of wait.
The unit test looks lilke this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TestApplication.class)
#FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class CustomRestTemplateTest {
#Autowired
private RestTemplate customRestTemplate;
#ClassRule
public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(SimulationSource.dsl(
service("www.test.com")
.get("/")
.willReturn(success(HttpBodyConverter.json("{}")).withDelay(10, TimeUnit.SECONDS))
));
#Test
public void connectionTimeoutTest() {
customRestTemplate.getForObject("www.test.com", Object.class);
}
}
The issue
As I mentioned in section The background, when my spring boot project starts, it will grab configs from spring cloud config server, but Hoverfly captured that request and try to find the corresponding record, of course it can't , because I only defined the records for my unit test(e.g. www.test.com), so it throws error:
{"destination":"172.16.2.84:8888","error":"No match found","key":"a7ac72c9bcc3dc2b76bf0877d98f9e3a","level":"warning","method":"GET","msg":"Failed to find matching request template from template store","path":"************","query":"","time":"2017-03-08T20:55:28+08:00"}
How could I fix this? I want use hoverfly, can I set some config and exclude config server's url?
Hoverfly's developer Tommy responded me in their email list
It's a known issue: https://github.com/SpectoLabs/hoverfly-java/issues/19
Update
This has been fixed by Tommy Situ, and the code fix will be release in v0.4.3

Categories

Resources