• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

강의 들으면서 응용해서 테스트를 작성하고 있는데 자꾸 null인 상태라고 해서 질문드립니다.

23.02.21 19:02 작성 23.02.23 17:26 수정 조회수 914

1

강의를 들으면서 간단한 테스트 문을 작성해봤습니다.
근데 계속 NullPointerException이 발생해서 여러가지 테스트를 하면서 오류를 분석해보니answerList에 아무 값도 들어가지 않아서
자꾸 null이라고 뜨는데 대체 왜 그런지 모르겠어서 질문드립니다.


아래에는 오류 화면이고 Question, Answer 엔티티와 함께
테스트 코드도 같이 첨부하겠습니다.
질문 조회 오류 콘솔.png

@Entity
@Getter @Setter
public class Question {

    @Id @GeneratedValue
    @Column(name = "question_id")
    private Long id;

    @Column(unique = true, length = 200)
    private String subject;

    @Column(unique = true, columnDefinition = "TEXT")
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
    private List<Answer> answerList;

    private LocalDateTime createDate;
}
@Entity
@Getter @Setter
public class Answer {

    @Id @GeneratedValue
    @Column(name = "answer_id")
    private Long id;

    @Column(columnDefinition = "TEXT")
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "question_id")
    private Question question;

    private LocalDateTime createDate;
}
@SpringBootTest
@Transactional
class AnswerRepositoryTest {

    @Autowired
    private QuestionRepository questionRepository;

    @Autowired
    private AnswerRepository answerRepository;

    @Test
    public void 답변조회() throws Exception{
        //given
        Question question = new Question();
        question.setSubject("제목입니다.");
        question.setContent("본문입니다.");
        questionRepository.save(q);
        Question findQuestion = questionRepository.findById(question.getId()).get();


        Answer answer = new Answer();
        answer.setContent("답변입니다.");
        answer.setQuestion(findQuestion);
        answer.setCreateDate(LocalDateTime.now());
        answerRepository.save(answer);

        //when
        List<Answer> answerList = findQuestion.getAnswerList();

        //then
        assertEquals("답변입니다.",answerList.get(0).getContent());
    }
}

테스트 코드에서 findQuestion.getSubject/ findQuestion.getContent까지 다 잘 찾아지는데
딱 getAnswerList()만 하면 null이라고 뜹니다

답변 1

답변을 작성해보세요.

0

y2gcoder님의 프로필

y2gcoder

2023.02.24

안녕하세요, 지호 님. 공식 서포터즈 y2gcoder 입니다.

먼저 강의와는 다르게 설계된 코드고 제공해주신 정보로만 판단했기 때문에 답변이 잘못되었을 수도 있다는 점 참고해주십시오!

또한 예외가 발생했을 때는 전체 예외 트레이스를 제공해주셔야 더 정확한 파악이 가능합니다 :)

보기에 Answer와 Question이 다대일 양방향 연관관계로 보입니다. 제가 봤을 때는

@SpringBootTest
@Transactional
class AnswerRepositoryTest {

    @Autowired
    private QuestionRepository questionRepository;

    @Autowired
    private AnswerRepository answerRepository;

    @Test
    public void 답변조회() throws Exception{
        //given
        Question question = new Question();
        question.setSubject("제목입니다.");
        question.setContent("본문입니다.");
        questionRepository.save(question); //오타이신 것 같습니다.
        


        Answer answer = new Answer();
        answer.setContent("답변입니다.");
        answer.setQuestion(question);//save하고 나면 question 객체가 어차피 영속화 상태라 이렇게 가능합니다. 
        answer.setCreateDate(LocalDateTime.now());
        answerRepository.save(answer);

        Question findQuestion = questionRepository.findById(question.getId()).get(); //그 후 이 친구를 다시 조회해보시길 바랍니다.

        //when
        List<Answer> answerList = findQuestion.getAnswerList();

        //then
        assertEquals("답변입니다.",answerList.get(0).getContent());
    }
}

이런 식으로 한 번 고치고 테스트해보는 것이 어떠십니까?

성공했는데 성공한 이유를 모르시겠다면 양방향 연관관계를 키워드로 좀 더 학습해보시길 권장드립니다. 특히 연관관계 편의 메서드 부분을 주의깊게 다시 봐주십시오!

또한 디버깅을 해보시는 것도 도움이 되실 것 같습니다. 처음 findQuestion 조회하는 부분에 breakpoint를 걸고 getAnswerList() 하는 부분까지 오시면서 체크해보시는 것도 도움이 되실 것 같습니다.

혹시나 실패한다면 예외 전문과 함께 질문 남겨주시면 답변드리겠습니다:)

감사합니다.

지호님의 프로필

지호

질문자

2023.02.25

답변감사합니다. 우선 위에서 남겨주신 코드도 제가 한 번 다 해본거라 여전히 안됐습니다.
그래서 말씀하신대로 디버깅을 해봤는데 answer에는 question이 들어갔는데
question에는 answerList가 null이라고 뜹니다. 우선 디버깅한 것부터 보여드리겠습니다.

image

그리고 예외 전문입니다.

2023-02-25 15:51:30.387  INFO 5672 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@47a5b70d testClass = AnswerRepositoryTest, testInstance = springproject.board.repository.AnswerRepositoryTest@c6653e, testMethod = 답변전체조회@AnswerRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@424fd310 testClass = AnswerRepositoryTest, locations = '{}', classes = '{class springproject.board.BoardApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@19b843ba, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2415fc55, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@cd1e646, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2bec854f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@23202fce, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@1c72da34], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4c81e7c2]; rollback [true]
2023-02-25 15:51:30.596  INFO 5672 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@47a5b70d testClass = AnswerRepositoryTest, testInstance = springproject.board.repository.AnswerRepositoryTest@c6653e, testMethod = 답변전체조회@AnswerRepositoryTest, testException = java.lang.NullPointerException, mergedContextConfiguration = [WebMergedContextConfiguration@424fd310 testClass = AnswerRepositoryTest, locations = '{}', classes = '{class springproject.board.BoardApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@19b843ba, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2415fc55, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@cd1e646, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2bec854f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@23202fce, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@1c72da34], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

java.lang.NullPointerException
	at springproject.board.repository.AnswerRepositoryTest.답변전체조회(AnswerRepositoryTest.java:71)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

그런데 테스트 코드만 이렇게 오류가 나고 answerList를 쓰는 다른 코드에서는 애플리케이션을 실행하면 문제없이 다 잘 나옵니다.

 

y2gcoder님의 프로필

y2gcoder

2023.02.25

이미 다 해보셨군요 ㅎㅎ

혹시 Question의 answerList를 초기화하는 코드 + 양방향 연관관계 편의 메서드 사용하는 게 실제 코드랑 다르게 하고 계시진 않을까요?

y2gcoder님의 프로필

y2gcoder

2023.02.25

위에 답글드렸던 부분이 우려되어 제가 직접 프로젝트를 만들고 보니, 양방향 연관관계임에도 연관관계 편의 메서드가 없고, question의 answerList를 초기화하는 부분이 테스트코드에는 보이지 않습니다.

그걸 보고 위에 질문에 올려주셨던 Answer, Question 코드를 보니 진짜 해당 부분이 없는 것을 발견했습니다.

그래서 해당 부분을 제가 추가해서 테스트해봤습니다.

@Getter
@Setter
@Entity
public class Question {

    @Id
    @GeneratedValue
    @Column(name = "question_id")
    private Long id;

    @Column(unique = true, length = 200)
    private String subject;

    @Column(unique = true, columnDefinition = "TEXT")
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "question")
    private List<Answer> answerList = new ArrayList<>(); //초기화하지 않으면 나중에 편의 메서드에서 에러가 발생합니다.

    private LocalDateTime createDate;

}
@Getter
@Setter
@Entity
public class Answer {
    @Id
    @GeneratedValue
    @Column(name = "answer_id")
    private Long id;

    @Column(columnDefinition = "TEXT")
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "question_id")
    private Question question;

    private LocalDateTime createDate;

    //연관관계 편의 메서드
    //최대한 원본 코드에서 고치지 않기 위해 setter에 줬습니다.
    public void setQuestion(Question question) {
        if (this.question != null) {
            this.question.getAnswerList().remove(this);
        }
        this.question = question;
        question.getAnswerList().add(this);
    }
}

그 후 테스트를 돌리면 통과하게 됩니다.

image

양방향 연관관계라 그런지 신경써줘야 할 게 많습니다. ㅎㅎ

윗 댓글에도 설명드렸듯이 양방향 연관관계에 대해 꼭 학습해보시길 권장드립니다. 또한 setter의 사용을 줄여보시고 생성자나 빌더 패턴을 조합해보시면 이런 문제가 생길 여지가 조금 더 줄어들 것 같습니다.

 

지호님의 프로필

지호

질문자

2023.02.25

늦은 시간까지 정성들여서 답변해주셔서 정말 감사드립니다 ㅠㅠ 말씀하신대로 양방향 연관관계에 대해서 다시 한번 보겠습니다. 진짜 정말 감사드립니다 ㅠㅠ

y2gcoder님의 프로필

y2gcoder

2023.02.26

파이팅입니다!