본문 바로가기

개발노트/Ruby

Rspec 에서 "여러" 인스턴스가 지정한 메소드를 실행하는지에 대한 Assertion

이번에 rails 에서 aws-sdk gem 을 이용하여 sns 를 사용하는 코드를 만들고 있다.

그런데 어떠한 이유로 aws 의 상태가 좋지 않거나, 인터넷 상태가 좋지 않는 등의 이유로 해당 로직이 실패할 수 있을 것 같아, begin ~ rescue 문법을 이용해 retry 로직을 구현하였다.

begin 블럭안에서 처리하는 로직에서 Aws::SNS::Topic 클래스의 인스턴스를 retry 할 때마다 새로 생성하도록 했는데 (처음에는 singleton 으로 했다가, thread safeness 를 보장한다는 내용을 찾을 수 없어서, local variable 로 매번 인스턴스 생성하도록 처리하게 하였음) 여러 인스턴스가 생긴 것에 대해서 Rspec 에서는 자주 쓰이는 expect 나 expect_any_instance_of 와 같은 메소드로는 제대로 테스트가 안 되는 문제가 생겼었다.

expect_any_instance_of 는 그 자체 뜻과는 달리(보는 사람에 따라 semantic 하게 볼 수도 있을 것 같긴 함) 오직 1개의 instance 에 대해서만 matcher 를 통해 assertion 하도록 되어있고, 2개 이상의 인스턴스가 생성되면 이미 1개의 인스턴스가 존재하기 때문에 오류가 발생한다.

The message 'X' was received by #<Instance:1> but has already been received by #<Instance:2>)

 

이를 우회하려면, 결국 mocking 을 해줘야 한다.

      context 'AWS 에 문제가 있는 경우' do
        it 'retry 를 수행하고, retry 후에도 실패하면 예외를 전파한다' do
          double = instance_double(Aws::SNS::Topic)
          allow(double).to receive(:publish).and_raise(Aws::Errors::ServiceError)
          allow_any_instance_of(Aws::SNS::Resource).to receive(:topic).and_return(double)

          expect(double).to receive(:publish).with(expected_args).at_least(3).times

          expect { subject.publish }.to raise_exception(StandardError)
        end
      end

이런식으로 test double 을 수동으로 만들어준 다음, 테스트 코드 안에서만 오직 하나의 인스턴스만 생성되도록 상황을 주어준(given) 다음, `at_least(3).times` 와 같이 몇번 호출됐는지 assertion 되도록 해야한다.

다른 de facto 의 올바른 방법이 있거나 workaround 가 있는지는 좀 더 찾아봐야겠지만, 1시간 넘게 찾아본 바로는 없었다.

expect_any_instance_of 란 말이 너무 애매함. T^T