ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • jest에서 모킹함수 'toHaveBeenCalledWith' 의 이상한점
    Nest.js/TDD 2024. 3. 26. 17:24
    728x90

    https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-

    특정 인수를 사용하여 모의 함수가 호출되었는지 확인하는 데 사용됩니다.

     

    test('registration applies correctly to orange La Croix', () => {
      const beverage = new LaCroix('orange');
      register(beverage);
      const f = jest.fn();
      applyToAll(f);
      expect(f).toHaveBeenCalledWith(beverage);
    });

     

    특정 인수를 모의함수가 호출되었는지 테스트 하는거라고 공식문서에 나와있는데, e2e테스트를 공부하던중에 이상한 버그가 있었다.

     

    it('(GET) check correct parameter', async () => {
          const postServSpy = jest.spyOn(postsService, 'getPostById');
          await request(app.getHttpServer())
            .get(`/posts/${mockData.id}`)
            .expect(HttpStatus.OK);
    
          expect(postServSpy).toHaveBeenCalledWith(mockData.id);
        });

     

    생성된 mockData의 id값을 parameter로 전송하고 그걸 controller에서 service의 인수로 전달되니 당연히 mockData.id를 toHaveBeenCalledWith하면 정상적이다.

     

    it('(POST) check correct parameter', async () => {
          const createPost: CreatePostDto = {
            author: 'bug',
            title: 'bug',
            content: 'bug',
          };
    
          const postServSpy = jest.spyOn(postsService, 'createPost');
    
          await request(app.getHttpServer())
            .post(`/posts`)
            .send(createPost)
            .expect(HttpStatus.CREATED);
    
          expect(postServSpy).toHaveBeenCalledWith(createPost);
        });

     

    author bug title bug content bug 를 body로 전달하고 그걸 controller에서 service로 전달한다.
    그리고 postsService의 createPost의 메서드를 모킹한 postServSpy의 인수가 잘 호출됬는지 테스트하는 toHaveBeenCalledWith 함수를 호출하니 다음과 같은 오류가 발생했다.

     

    PostsController E2E Test › (POST) /posts › (POST) check correct parameter
    
        expect(jest.fn()).toHaveBeenCalledWith(...expected)
    
        - Expected
        + Received
    
          Object {
            "author": "bug",
        +   "commentCount": 0,
            "content": "bug",
        +   "created_at": 2024-03-26T15:11:34.938Z,
        +   "id": 493,
        +   "likeCount": 0,
            "title": "bug",
        +   "updated_at": 2024-03-26T15:11:34.938Z,
          },
    
        Number of calls: 1
    
          149 |         .expect(HttpStatus.CREATED);
          150 |
        > 151 |       expect(postServSpy).toHaveBeenCalledWith(createPost);
              |                           ^
          152 |     });
          153 |
          154 |     // e2e 3-2 (POST) Create Post /posts
    
          at Object.<anonymous> (posts.e2e-spec.ts:151:27)

     

    코드상으로 말이 안된다. controller와 service 코드는 아래와 같다.

     

    // create-post-dto.ts
    export class CreatePostDto {
      author: string;
      title: string;
      content: string;
    }
    
    
    // posts.controller.ts
    @Post()
      async postPosts(@Body() dto: CreatePostDto) {
        return await this.postsService.createPost(dto);
     }
      
     // posts.service.ts
     async createPost(createPostArgs: CreatePostDto) {
        return await this.postsRepository.createPostRepository(createPostArgs);
      }

     

    author, title, content 프로퍼티만 전달 했는데 왜 마치 생성된 entity의 모든 프로퍼티들이 arguments로 전달된것처럼 보일까

     

    • 원인은 posts.repository에 있었다.

     

    (method) PostsRepository.createPostRepository(createPostArgs: CreatePostDto): Promise<CreatePostDto & PostsModel>

     

    리턴값의 추론이 Promise<CreatePostDto & PostsModel> 였다.
    해당 repository의 코드를 수정 해주었더니 해결은 되었다.

     

    async createPostRepository(createPostArgs: CreatePostDto) {
        return await this.repository.save({
          content: createPostArgs.content,
          title: createPostArgs.title,
          author: createPostArgs.author,
        });
      }

     

    근데 이해가 안되는점은, toHaveBeenCalledWith 함수는 특정 인수를 사용하여 모의 함수가 호출 되었는지 확인 하는 함수인데 도대체 리턴값을 바꾸는거랑 무슨 상관이지??? 이건 마치 toEqual를 말하는것 같다.

     

    Use .toHaveBeenCalledWith
     to ensure that a mock function was called with specific arguments. The arguments are checked with the same algorithm that .toEqual uses.

     

    공식 문서에 .toEqual랑 같은 알고리즘을 사용했다고 나와있는데, 이거랑 무언가 연관있는 버그일까 모르겠다.
    결론적으로 toHaveBeenCalledWith 모킹 함수가 toEqual처럼 동작하는 버그가 있었다.

     

     

    • 코드를 아래처럼 바꾸어도 테스트가 정상적으로 성공한다.
    async createPostRepository(createPostArgs: CreatePostDto) {
        const post = await this.repository.save({
          content: createPostArgs.content,
          title: createPostArgs.title,
          author: createPostArgs.author,
        });
    
        return this.getPostByIdRepository(post.id);
      }

     

    (method) PostsRepository.createPostRepository(createPostArgs: CreatePostDto): Promise<CreatePostDto & PostsModel>
    왜 createPostRepository의 메서드를 저런 반환값으로 호출하면 저런 버그가 발생하는걸까
    serivce에서 해당 메서드 말고 getPostsRepository 메서드를 호출하게 바꾸어도 테스트는 정상적으로 동작한다.

     

    // posts.e2e-spec.ts
    it('(POST) check correct parameter', async () => {
          const createPost: CreatePostDto = {
            author: 'bug',
            title: 'bug',
            content: 'bug',
          };
    
          const postServSpy = jest.spyOn(postsService, 'createPost');
    
          await request(app.getHttpServer())
            .post(`/posts`)
            .send(createPost)
            .expect(HttpStatus.CREATED);
    
          expect(postServSpy).toHaveBeenCalledWith(createPost);
        });
        
    // posts.controller.ts
    @Post()
      async postPosts(@Body() dto: CreatePostDto) {
        return await this.postsService.createPost(dto);
    }
    
    // posts.service.ts
    async createPost(createPostArgs: CreatePostDto) {
        return await this.postsRepository.getPostsRepository();
    }
    
    // posts.repository.ts
    async getPostsRepository() {
        return await this.repository.find();
    }

     

     

    내가 이해를 못하고 있는건지 구글링 아무리해도 안나온다. 찾게 된다면 글에 추가 할것....
    728x90
Designed by Tistory.