MockMvc SpringBoot integration tests passing individually but failing when running consecutively? (DataIntegrityViolation)
I have developed integration tests using MockMvc to send requests to the API server.
When I run each test individually they pass, but if I run the entire test class, the first passes and the rest fail due to:
DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PUBLIC.UK_SB8BBOUER5WAK8VYIIY4PF2BX_INDEX_2 ON PUBLIC.USER(USERNAME) VALUES 4"; SQL statement:
I understand that this means the tests are trying to add another User entity to the repo where one already exists, but I don't understand why the repo is not being reset after each test?
The tests rely on methods to avoid repeated code in each test, and this must be having an effect because only my test classes with these methods are failing like this, but I cannot think why or how to fix it.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GameControllerTest {
@Autowired
MockMvc mvc;
@Autowired
GameService gameService;
@Autowired
GameRepository gameRepository;
@Autowired
UserRepository userRepository;
@Autowired
UserService userService;
public String username1 = "ss";
public String username2 = "dd";
public String username3 = "ff";
public String location = "London";
public String password = "Password72-";
public String apiVersion = "v1";
public final String gamesUrl = "/api/" + apiVersion + "/games/";
public final String addGameUrl = gamesUrl + "add-game";
public final String gameHistoryUrl = gamesUrl + "your-game-history";
public final String signupUrl = "/api/" + apiVersion + "/users/signup";
public final String loginUrl = "/api/" + apiVersion + "/login";
@Test
@Transactional
public void addGameTest() throws Exception {
String token = signupAndLogin();
Set<User> users = new HashSet<>();
User user = userRepository.findByUsername(username1);
User user2 = userRepository.findByUsername(username2);
users.add(user);
users.add(user2);
Game game = new Game(users, username1, username2, ResultForWhite.LOSE.getUrl(), "1.e4e5", username2);
MvcResult mvcResult = mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game)).header("Authorization", token))
.andExpect(status().isOk()).andReturn();
//Test JSON response is correct.
String jsonResult = mvcResult.getResponse().getContentAsString();
assertThat(jsonResult.contains(username1));
assertThat(jsonResult.contains(username2));
//Test that Game stored in DB correctly.
Optional<Game> savedGame = gameRepository.findById(1L);
assertThat(savedGame.get().getGameId() == 1);
//Test that user victories and defeats have been incremented correctly.
//Should be 1.
User savedUser = userRepository.findByUsername(username1);
User savedUser2 = userRepository.findByUsername(username2);
UserStats savedUserStats = savedUser.getUserStats().get(0);
UserStats savedUserStats2 = savedUser2.getUserStats().get(0);
Integer user1Victories = savedUserStats.getVictories();
Integer user1Defeats = savedUserStats.getDefeats();
Integer user2Victories = savedUserStats2.getVictories();
Integer user2Defeats = savedUserStats2.getDefeats();
//Assert that magnus lost the game and his defeats was incremented, and victories unchanged.
assertThat(user1Victories == 0 && user1Defeats == 1);
//Assert that hikaru won the game and his victories was incremented, and defeats unchanged.
assertThat(user2Victories == 1 && user2Defeats == 0);
}
@Test
@Transactional
public void getAUsersCompleteGameHistory() throws Exception {
String token = signupAndLogin();
createGames(token);
String content = "{\"keyword\":\"null\"}";
MvcResult mvcResult = mvc.perform(post(gameHistoryUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(content).header("Authorization", token))
.andExpect(status().isOk()).andReturn();
String jsonResult = mvcResult.getResponse().getContentAsString();
System.out.println(jsonResult);
//Ensure the JSON object returned contains all the game entities.
assertTrue(jsonResult.contains("{\"gameId\":1") || jsonResult.contains("{\"gameId\":2") || jsonResult.contains("{\"gameId\":3") || jsonResult.contains("{\"gameId\":4") || jsonResult.contains("{\"gameId\":5") || jsonResult.contains("{\"gameId\":6"));
}
public String signupAndLogin() throws Exception {
User user = new User(1L, username1, password, location);
mvc.perform(post(signupUrl)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andExpect(status().isOk());
Optional<User> savedUser = userService.getUser(1L);
System.out.println(savedUser.toString());
//2nd User
User user2 = new User(2L, username2, password, location);
mvc.perform(post(signupUrl)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user2)))
.andExpect(status().isOk());
Optional<User> savedUser2 = userService.getUser(2L);
System.out.println(savedUser2.toString());
//3rd User
User user3 = new User(3L, username3, password, "Bristol");
mvc.perform(post(signupUrl)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user3)))
.andExpect(status().isOk());
Optional<User> savedUser3 = userService.getUser(3L);
System.out.println(savedUser3.toString());
//LOGIN
MvcResult mvcResult = mvc.perform(post(loginUrl)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andExpect(status().isOk())
.andReturn();
String token = mvcResult.getResponse().getHeader("Authorization");
System.out.println("Bearer Token: " + token);
return token;
}
@Transactional
public void createGames(String token) throws Exception {
Set<User> users = new HashSet<>();
Set<User> users2 = new HashSet<>();
User user = userRepository.findByUsername(username1);
User user2 = userRepository.findByUsername(username2);
User user3 = userRepository.findByUsername(username3);
System.out.println("ss: " + username1 + " dd " + username2 + " ff: " + username3);
//Add the User objects
users.add(user);
users.add(user2);
//Add the User objects
users2.add(user);
users2.add(user3);
System.out.println(users2.toString());
//Create 4 new Game objec
Game game = new Game(users, username1, username2username2);
Game game2 = new Game(users, username1, username2, username2);
Game game3 = new Game(users, username2, username1, username2);
Game game4 = new Game(users, username2, username1,username2);
//Create 2 new Game objects
Game game5 = new Game(users2, username3, username1, username3);
Game game6 = new Game(users2, username1, username3, username3);
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game)).header("Authorization", token))
.andExpect(status().isOk());
System.out.println("DEFEATS TEST : //////// :" + userRepository.findByUsername(username2).getUserStats().get(0).getDefeats());
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game2)).header("Authorization", token))
.andExpect(status().isOk());
System.out.println(userRepository.findByUsername(username2).getUserStats().get(0).getVictoryScore());
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game3)).header("Authorization", token))
.andExpect(status().isOk());
System.out.println(userRepository.findByUsername(username2).getUserStats().get(0).getVictoryScore());
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game4)).header("Authorization", token))
.andExpect(status().isOk());
System.out.println(userRepository.findByUsername(username2).getUserStats().get(0).getVictoryScore());
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game5)).header("Authorization", token))
.andExpect(status().isOk());
mvc.perform(post(addGameUrl)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(asJsonString(game6)).header("Authorization", token))
.andExpect(status().isOk());
}
I am not sure but perchance if I refactored this to use @BeforeEach it would work? I just don't know if it will work though.
I have looked into @BeforeEach and I am unsure how I could get the token from the signUp method call e.g:
@BeforeEach
void setup( WebApplicationContext wac) throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup( wac )
.apply(SecurityMockMvcConfigurers.springSecurity())
.alwaysDo( signupAndLogin() )
.build();
}
Thanks
Edit: I have added this to the 2nd test:
@Test
public void getAUsersCompleteGameHistory() throws Exception {
userRepository.deleteAll();
gameRepository.deleteAll();
//This doesn't print anything.
for (User u: userRepository.findAll()
) {
System.out.println();
System.out.println(u.getUsername());
System.out.println();
}
//This passes, user Repo.findAll IS empty
assertTrue(userRepository.findAll().isEmpty());
assertTrue(gameRepository.findAll().isEmpty());
String token = signupAndLogin();
createGames(token);
ETC...
}
So the repo are empty, it must be something in signUpAndLogin?
Solution 1:
You can put @Transactional
on your test class, instead of every test case, if all of the test require a rollback. I am not sure why it doesn't work in your case, so i will offer you some workarounds.
You can do a cleanup after every test:
@AfterEach
public void cleanup() {
gameRepository.deleteAll();
userRepository.deleteAll();
}
That is assuming that your repositories do not contain previous data required to run tests correctly. Or create custom method to delete by username in your repo, to delete only users created for tests, for example:
userRepository.deleteByUsername(username);
In order to initialize the token in BeforeEach method, and then access it, you need to keep it in an instance variable:
@BeforeEach
public void setup() throws Exception {
//instance variable
this.token = signupAndLogin();
}
But that will also lead to constraint violation if the DB is not cleared. If it's the username constraint which causes the problem, you can somewhat hack it by assigning random values to usernames in tests - UUID.randomUUID().toString()
for example.
As a side note, i believe assertions using assertThat should look like that:
assertThat(user2Victories == 1 && user2Defeats == 0).isEqualTo(true);
assertThat(user2Victories == 1 && user2Defeats == 0);
actually does not assert anything.