c-x-berger
c-x-berger

Reputation: 1051

How can I test a Spring controller that uses JDBC?

I have a Spring controller that looks like this:

@RestController
@RequestMapping("/foo")
public class FooController {
    @Autowired
    private NamedParameterJdbcTemplate template;

    @GetMapping("/{name}")
    public List<Foo> byName(@PathVariable String name) {
        Map<String, String> params = new HashMap<>();
        params.put("name", name);
        List<Foo> result = template.query("SELECT * FROM FOOS WHERE FOO_NM = :name", params, new FooRowMapper());
        if (result.size() == 0) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("foo %s not found", name));
        }
        return result;
    }
}

However, I'm not sure how to test this. I can do a basic "Spring can set it up" test:

@SpringBootTest
public class FooControllerTest {
    @Autowired
    private FooController controller;

    @Test
    public void canCreate() {
        Assertions.assertNotNull(controller);
    }
}

But I'm not certain what the right way to test, for example, the byName method is. Do I need to mock something? Can I just test it like a plain Java method (call it with whatever parameters and assert the output)?

Upvotes: 2

Views: 547

Answers (5)

Alexandre Couzi
Alexandre Couzi

Reputation: 38

I'm not the best at all for testing my controllers (I also started not too long ago) And well, I mockup my controller.

class UserControllerTest {

@Autowired
private MockMvc mockMvc;

@Test
void addUserTest() throws Exception {
    String content = "{" +
            "\"username\": \"test\"," +
            "\"password\": \"tesT1234\"," +
            "\"email\": \"[email protected]\"" +
            "}";

    mockMvc.perform(
            MockMvcRequestBuilders.post("/users/signup")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(content))
            .andExpect(status().isCreated());
}

For example here for a signup user, I build a JSON content and send it to my mock. I check the final status to see if it creates the user.

Upvotes: 1

c-x-berger
c-x-berger

Reputation: 1051

After a bit of research and help from the posted answers, I moved the retrieval code into a service:

@Service
public class FooService implements IFooService {
    @Autowired
    private NamedParameterJdbcTemplate template;

    // the only method of IFooService
    @Override
    public List<FormulaFunction> getAllByName(String name) {
        Map<String, String> params = new HashMap<>();
        params.put("name", name);
        return template.query("SELECT * FROM FOOS WHERE FOO_NM = :name", params, new FooRowMapper());
    }
}

In testing the controller, that service is mocked using Mockito:

@WebMvcTest(FooController.class)
public class FooControllerTest {
    @MockBean
    private IFooService service;

    @Autowired
    private FooController controller;

    @Autowired
    private MockMvc mvc;

    @Test
    public void canCreate() {
        Assertions.assertNotNull(controller);
    }

    /**
     * Test that controller properly returns 404 when no results are found
     */
    @Test
    public void test404() throws Exception {
        doReturn(new ArrayList<>()).when(service).getAllByName("bars");
        this.mvc.perform(get("/foo/bars")).andExpect(status().isNotFound());
    }
}

Upvotes: 0

Salavat Yalalov
Salavat Yalalov

Reputation: 114

It is a bad idea to make a controller that uses JDBC itself. It's much better to use Controller-Service-Repository pattern.

So your code in the Foo controller can be like:

@RestController
@RequestMapping("/foo")
public class FooController {
  @Autowired
  private FooService fooService;
  
  @GetMapping("/{name}")
  public ResponseEntity<?> byName(@PathVariable String name) {
    final List<Foo> list = fooService.getFooByName(name);
    return ResponseEntity.ok(list);
  }
}

For testing purposes it's better to use MockMvc:

// In the Foo controller
@GetMapping("/{name}")
    public ResponseEntity<?> byName(@PathVariable String name) {
        return ResponseEntity.ok("Hello, " + name);
}

// In the Test class
@SpringBootTest
@AutoConfigureMockMvc
class ControllerTests {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private FooController fooController;

    @Test
    public void contextLoads() {
        assertThat(fooController, is(notNullValue()));
    }

    @Test
    public void getUser() throws Exception {
        MockHttpServletResponse response = mockMvc.perform(MockMvcRequestBuilders.get("/foo/Joe"))
                .andExpect(status().isOk())
                .andDo(print())
                .andReturn().getResponse();

        assertThat(response.getContentAsString(), is("Hello, Joe"));
    }

}

Upvotes: 1

V&#252;sal
V&#252;sal

Reputation: 2706

Do I need to mock something?

It depends on what exactly you need to test. If you write a unit test - then you usually mock external dependencies as you assume, that they will follow some contract.

Can I just test it like a plain Java method (call it with whatever parameters and assert the output)?

Of course, you can, but it will mean that you're testing a bean method, but not the full flow from receiving a http request and returning the response.

If you need to do an end-to-end test you need to run your app and then call your HTTP methods and verify response. For that you can use MockMvc as described in one of the answers.

Upvotes: 1

k-wasilewski
k-wasilewski

Reputation: 4613

You can test it like a regular Java method or use MockMvc to perform a get request to the relewant path.

It would be nice if you posted the body of your method (right now it’s hard to help).

Upvotes: 0

Related Questions