스프링 부트 프로젝트(레스토랑 예약 사이트 만들기)-의존성 주입 10강 해석

Psj·2021년 12월 20일
0

#1 의존성 주입 사용(1)

main/~/domain/RestaurantRepository.java

package kr.co.fastcampus.eatgo.domain;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component // 스프링이 관리할수 있게 @Component를 붙인다. 그럼 Autowired가 사용되는곳에서 이 @Component가 붙어있는곳을찾아 자동으로 의존성주입을 하게된다.
public class RestaurantRepository {

    private List<Restaurant> restaurants = new ArrayList<>(); // 가게목록 담을 리스트

    public RestaurantRepository(){ // 생성자를 이용해 가게 객체 추가
        restaurants.add(new Restaurant(1004L,"Bob zip", "Seoul"));
        restaurants.add(new Restaurant(2020L, "Cyber Food", "Seoul"));
    }

    public List<Restaurant> findAll() { // 가게목록 반환 
        return restaurants;
    }

    public Restaurant findById(Long id) { // 특정가게 상세 반환

        return restaurants.stream()
                .filter(r -> r.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}

클래스명 상단에 스프링이 관리할수 있게 @Component를 붙인다. 그럼 Autowired가 사용되는곳에서 이 @Component가 붙어있는곳을찾아 자동으로 의존성주입을 하게된다.

#2 의존성 주입 사용(1)

/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository repository; 

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = repository.findAll(); // repository.findAll() 메서드로 가게목록 반환

        return restaurants;
    }

    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = repository.findById(id); // repository.findById(id) 메서드로 특정 가게상세 반환

        return restaurant;
    }

}

기존
private RestaurantRepository repository = new RestaurantRepository();

변경
@Autowired
private RestaurantRepository repository;

#3 테스트에서 의존성주입 사용법

/test/~/interfaces/RestaurantControllerTest.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {

    @Autowired
    private MockMvc mvc; // MockMvc생성

    @SpyBean // 이것으로 컨트롤러에 우리가 원하는 객체를 주입할수있다. (@Autowired 테스트를 하기위한것)
    private RestaurantRepository restaurantRepository;

    @Test
    public void list() throws Exception {
        mvc.perform(get("/restaurants"))
        .andExpect(status().isOk())
        .andExpect(content().string(containsString("\"id\":1004")))
        .andExpect(content().string(containsString("\"name\":\"Bob zip\"")));

    }

    @Test
    public void detail() throws Exception {
        mvc.perform(get("/restaurants/1004"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":1004")))
                .andExpect(content().string(containsString("\"name\":\"Bob zip\"")));

        mvc.perform(get("/restaurants/2020"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":2020")))
                .andExpect(content().string(containsString("\"name\":\"Cyber Food\"")));
    }
}

@SpyBean
private RestaurantRepository restaurantRepository;

테스트에 이렇게 넣어줘야 @Autowired 의존성주입 테스트가 정상작동하는것을 확인할 수 있다.

의존성주입을 사용해야 되는이유

우리가 사용해야되는 객체를 다양하게 변경할 수 있다.
이것을 이용해서 같은 방식으로 작동하는 여러객체들을 효율적으로 관리할 수 있다.

이제 의존성주입 방식을 전체적으로 도입하기 위해 우리가 이전에 만들었던 main/~/domain/RestaurantRepository.java 를

main/~/domain/RestaurantRepository.java 인터페이스와
main/~/domain/RestaurantRepositoryImpl.java 이라는 구체적인 구현체로 나눌것이다.

#4 인터페이스와 구현체 분리

main/~/domain/RestaurantRepository.java

package kr.co.fastcampus.eatgo.domain;

import java.util.List;

public interface RestaurantRepository {
    List<Restaurant> findAll();

    Restaurant findById(Long id);
}

main/~/domain/RestaurantRepositoryImpl.java

package kr.co.fastcampus.eatgo.domain;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component // 스프링이 관리할수 있게 @Component를 붙인다. 그럼 Autowired가 사용되는곳에서 이 @Component가 붙어있는곳을찾아 자동으로 의존성주입을 하게된다.
public class RestaurantRepositoryImpl implements RestaurantRepository {

    private List<Restaurant> restaurants = new ArrayList<>(); // 가게목록 담을 리스트

    public RestaurantRepositoryImpl(){ // 생성자를 이용해 가게 객체 추가
        restaurants.add(new Restaurant(1004L,"Bob zip", "Seoul"));
        restaurants.add(new Restaurant(2020L, "Cyber Food", "Seoul"));
    }

    @Override
    public List<Restaurant> findAll() { // 가게목록 반환
        return restaurants;
    }

    @Override
    public Restaurant findById(Long id) { // 특정가게 상세 반환

        return restaurants.stream()
                .filter(r -> r.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}

/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import kr.co.fastcampus.eatgo.domain.RestaurantRepositoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository repository;
    //private RestaurantRepository repository = new RestaurantRepository(); // 중복되는 코드를 RestaurantRepository 클래스로 분리

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = repository.findAll(); // repository.findAll() 메서드로 가게목록 반환

        return restaurants;
    }

    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = repository.findById(id); // repository.findById(id) 메서드로 특정 가게상세 반환

        return restaurant;
    }

}

@Autowired
private RestaurantRepository repository;

컨트롤러에서 이부분을 보면 인터페이스를 이용하여 @Autowired로 객체생성을 자동으로하는 의존성주입 형태가 된것을 확인할 수 있다.

/test/~/interfaces/RestaurantControllerTest.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import kr.co.fastcampus.eatgo.domain.RestaurantRepositoryImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {

    @Autowired
    private MockMvc mvc; // MockMvc생성

    @SpyBean(RestaurantRepositoryImpl.class) // 아래의 인터페이스가 실질적으로 어떤 클래스를 구현하는지 명시해줘야한다.
    private RestaurantRepository restaurantRepository;

    @Test
    public void list() throws Exception {
        mvc.perform(get("/restaurants"))
        .andExpect(status().isOk())
        .andExpect(content().string(containsString("\"id\":1004")))
        .andExpect(content().string(containsString("\"name\":\"Bob zip\"")));

    }

    @Test
    public void detail() throws Exception {
        mvc.perform(get("/restaurants/1004"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":1004")))
                .andExpect(content().string(containsString("\"name\":\"Bob zip\"")));

        mvc.perform(get("/restaurants/2020"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":2020")))
                .andExpect(content().string(containsString("\"name\":\"Cyber Food\"")));
    }
}

테스트코드에서는 구현체 명시하여 테스트 진행

@SpyBean(RestaurantRepositoryImpl.class) // 아래의 인터페이스가 실질적으로 어떤 클래스를 구현하는지 명시해줘야한다.
private RestaurantRepository restaurantRepository;

테스트가 정상 작동하는것을 확인할 수 있다.

profile
Software Developer

0개의 댓글