Spring boot test without database connection - java

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.

Related

Semi-integration testing using mocks and application context

I need to write an e2e test on REST level, that sends real requests. So I want to use application context instead of mocking beans.
RestController.class has an autowired MyService.class, and this MyService.class is dependent on two repository classes. So I tried to mock repositories and inject them in the real Service in the following way:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = MyService.class)
#AutoConfigureMockMvc
class MyControllerTest {
#Mock private MyRepository repository;
#Mock private AnotherRepository anotherRepository;
#Autowired #InjectMocks private MyService service;
#InjectMocks private MyController controller;
#RepeatedTest(1)
void someTest() throws Exception {
MockHttpServletResponse response =
mvc.perform(...); assertThat(...);
}
}
#Service
#RequiredArgsConstructor
public class MyService {
private final MyRepository repository;
private final AnotherRepository another; ...}
But I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myRepository'
I also tried to use #ContextConfiguration(classes = {MyConfig.class }) with no success:
#EnableWebMvc
#Configuration
public class MyConfig {
#Autowired private MyService service;
#Mock private MyRepository repository;
#Mock private AnotherRepository another;
}
Is there something I'm missing?
First off you should not use #Mock which is relevant for plain unit testing with mockito, use #MockBean instead. #MockBean "belongs" to the spring universe, it will create a bean (backed by mockito after all) but also will place it into the application context. If there is another bean of this type - it will substitute it.
I also don't think you should use #ExtendWith because #SpringBootTest in the relatively recent spring boot versions already has it defined (check the source code of #SpringBootTest annotation to make sure that's the case indeed.
Now other mistakes are more complicated.
To start with you don't need a spring boot test at all here. It also has a wrong parameter actually (which should point on a configuration class).
You should use #WebMvcTest instead. It seems that you only want to test the controller, and mock the service. #SpringBootTest in general tries to mimick the loading of the whole application, whereas the #WebMvcTest only tests a web layer. BTW, with this approach you don't need to even mock the Repository class because the controller will use the mock of the service, and since you don't load the whole application context there is no need to define a repository at all (the service being a mock doesn't use it anyway)
Add #RunWith(SpringRunner.class) to class MyControllerTest
#RunWith(SpringRunner.class)
class MyControllerTest {
}

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 2.2.0 Testing : Configuration error: found multiple declarations of #BootstrapWith

#RunWith(SpringRunner.class)
#SpringBootTest(classes=MyApplication.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#WebAppConfiguration
#RestClientTest(Controller.class)
public class MyIntegrationTest {
}
when I run this I get the following error
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class
The root cause is you are using both #SpringBootTest and #RestClientTest together.
Because it causes the conflict about #BootstrapWith annotation. You can see the image below.
Tip: If you are using JUnit 4, don’t forget to also add
#RunWith(SpringRunner.class) to your test, otherwise the annotations
will be ignored. If you are using JUnit 5, there’s no need to add the
equivalent #ExtendWith(SpringExtension.class) as #SpringBootTest and
the other #…​Test annotations are already annotated with it.
Reference: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing
If your test case is simple, you can refer to the example using #RestClientTest here.
In my case, I define some complicated configurations related to the tested service (Ex: jpaAuditingHandler, jpaMappingContext, ...). That's why I use #SpringBootTest to auto-configure.
This is my code sample:
#SpringBootTest
class MyTest {
#Autowired
private MyProperties myProperties;
#Autowired
private MyService myService;
private MockRestServiceServer server;
#Autowired
#Qualifier("restTemplateBean1")
private RestTemplate restTemplate;
#BeforeEach
void setUp() {
server = MockRestServiceServer.createServer(restTemplate);
}
#Test
void testCallRestServiceSuccess() throws Exception {
this.server.expect(requestTo(myProperties.getUrl())).andRespond(withStatus(HttpStatus.OK));
boolean result = myService.callRestService();
assertThat(result).isTrue();
}
}
Some other references:
MockRestResponseCreators javadoc: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/client/response/MockRestResponseCreators.html
https://www.baeldung.com/restclienttest-in-spring-boot
It looks like you have at least one too many Spring test annotations on your test.
What exactly do you want to test? If it is, indeed, just a RestClientTest, then this should work:
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#RestClientTest(YourRestClient.class)
public class MyIntegrationTest {
}
Or, go with just #SpringBootTest, but I don't know exactly what you want to do here.

How can I use #WithMockUser and pass my username and password from my properties file?

I am writing an integration test where the application has a basic auth applied with spring security. I am using #WithMockUser to tell mockMVC how to authenticate with my endpoints.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#WithMockUser(username="username", password="password")
This works but I am wondering if I can replace those strings with references to the values in my application.properties file similiar how you can do:
#Value("${spring.security.user.name}")
private String userName;
Is the above possible?
I ended up removing the #WithMockUser annotation and went with this solution which worked better for my use case. This method authenticates with the configured basic auth so going forward if it was to change it wouldn't be a problem.
#RunWith(SpringRunner.class)
#SpringBootTest
public class sampleTestIT {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = webAppContextSetup(webApplicationContext).build();
}
....
}
You can pass only constant expressions to annotations but an alternative will be to create your own Security Context as explained here in the #WithSecurityContext chapter:
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html
I would also create my own test properties configuration file which will be used to read properties from the tests.

How to run a integration test in Spring with #SpringBootTest

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!");
}

Categories

Resources