Search code examples
springspring-testspring-webspring-test-mvc

Spring, different behaviour when testing


I'm experimenting with Spring Web and testing a REST controller. The application is basically a game database accessible through a web service.

When I launch it and test it with Postman to add a game, I get the behavior I'm looking for. However, when I test the controller with the SpringJUnit4ClassRunner, it seems the game I'm trying to add already exists in the database and I can't add it.

Here is my test class:

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(GameController.class)
public class GameControllerTest {

    @MockBean
    private IGameService gameService;

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void postGameTest() throws Exception {
        String mockGameJson = "{\"name\":\"Test Game\",\"description\":\"A test game.\"}";

        //Create a post request with an accept header for application\json
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .post("/game/")
                .accept(MediaType.APPLICATION_JSON).content(mockGameJson)
                .contentType(MediaType.APPLICATION_JSON);

        MvcResult result = mockMvc.perform(requestBuilder).andReturn();

        MockHttpServletResponse response = result.getResponse();

        //Assert that the return status is CREATED
        assertEquals(HttpStatus.CREATED.value(), response.getStatus());
    }
}

The assert in the last line fails because the status is a http 409 Conflict

I personally return that status in the controller:

@RestController
public class GameController {

    @Autowired
    private IGameService gameService;

    @PostMapping("/game")
    public ResponseEntity<String> addGame(@RequestBody Game game, UriComponentsBuilder builder) {
        boolean flag = gameService.addGame(game);
        if (!flag) return new ResponseEntity<>("Another game with this name already exists.", HttpStatus.CONFLICT);
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(builder.path("/game/{id}").buildAndExpand(game.getId()).toUri());
        return new ResponseEntity<>(headers, HttpStatus.CREATED);
    }
...

It doesn't make sense that that would happen because my database is supposed to be empty at the beginning of the test right? Here is the related service:

@Service
public class GameService implements IGameService { //Service layer

    @Autowired
    private IGameDAO gameDAO;

    @Override
    public synchronized boolean addGame(Game game) {
        if(gameDAO.gameExists(game.getName()))
            return false;
        else {
            gameDAO.addGame(game);
            return true;
        }
    }
...

And the DAO:

@Transactional
@Repository
public class GameDAO implements IGameDAO {

    @PersistenceContext
    private EntityManager entityManager;


    @Override
    public void addGame(Game game) {
        entityManager.persist(game);
    }

    @Override
    public boolean gameExists(String name) {
        String jpql = "from Game as g WHERE g.name = ?0 ";
        int count = entityManager.createQuery(jpql).setParameter(0, name).getResultList().size();
        return count > 0;
    } ...

And those are the dependencies in my build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.h2database:h2'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

What am i doing wrong here?


Solution

  • Solved it. As it was commented, it is better to test the layers independently. Here, I was trying to test my controller (web). The service is mocked so by default any invocation on a method that should return a boolean would return false.

    I had to tell the the mocked service to return true to adequately test the post method. Like that:

    given(gameService.addGame(any())).willReturn(true);