RSpec의 have_attributes 매처는 객체의 여러 속성 값을 동시에 검증하는 데 매우 유용합니다. 하지만 특정 속성, 특히 문자열 속성에 대해 복잡한 조건을 적용하고자 할 때 일반적인 매처의 조합만으로는 한계에 부딪힐 수 있습니다.
기존 include 매처 조합의 한계
작성자는 message 객체의 response 속성이 “Hello, world!”를 포함하면서도 “Subject:”는 포함하지 않아야 하는 복합적인 요구사항을 충족하기 위해 다음과 같은 코드를 시도했습니다.
ruby
message = Message.first
expect(message).to have_attributes(
conversation_id: conversation.id,
customer: "customer@example.com",
response: include("Hello, world!").and(not_to(include("Subject:")))
)
이 방식은 response 속성이 문자열임에도 불구하고 have_attributes의 컨텍스트 내에서 기대했던 대로 작동하지 않았습니다. include 매처는 일반적으로 컬렉션이나 문자열 내에 특정 요소/부분 문자열이 있는지 검사하지만, have_attributes 내부에서 다른 매처와 and, not_to와 같은 조합으로 사용될 때, 특히 문자열 속성에 대한 복합적인 ‘포함 및 미포함’ 조건을 명확하게 처리하는 데 어려움이 있었습니다. 이는 have_attributes가 각 속성에 대해 단일 매처를 기대하거나, 복합 매처의 동작 방식이 특정 컨텍스트에서 다르게 해석될 수 있기 때문입니다. 결과적으로, 이 시도는 원하는 검증 로직을 정확히 반영하지 못하고 실패했습니다.
satisfy 매처의 발견 및 활용
rspec-expectations gem의 소스 코드를 분석하던 중, 작성자는 satisfy 매처가 이러한 복잡한 검증 요구사항에 완벽하게 부합한다는 것을 발견했습니다. satisfy 매처는 주어진 블록이 true를 반환하면 테스트를 통과시키고, false를 반환하면 실패시키는 매우 유연한 방식으로 작동합니다. 이는 개발자가 어떤 속성에 대해서든 커스텀된 검증 로직을 Ruby 코드로 직접 구현할 수 있게 해줍니다.
satisfy 매처를 활용한 해결책은 다음과 같습니다. 원문 코드에서는 message: 속성으로 오타가 있었으나, 문맥상 response: 속성에 대한 검증이므로 이를 반영하여 설명합니다.
ruby
expect(message).to have_attributes(
conversation_id: conversation.id,
response: satisfy do |response_content|
response_content.include?("Hello, world!") && !response_content.include?("Subject:")
end
)
위 코드에서 satisfy 매처는 message 객체의 response 속성 값을 response_content라는 블록 인자로 받아 블록을 실행합니다. 블록 내부에서는 Ruby의 기본 문자열 메서드인 include?를 사용하여 “Hello, world!” 포함 여부와 “Subject:” 미포함 여부를 && 연산자로 동시에 검증합니다. 이 블록이 최종적으로 true를 반환하면 해당 response 속성 검증은 성공하게 됩니다. 이 방식은 have_attributes 내에서 Ruby 언어의 모든 기능을 활용하여 커스텀 로직을 직접 구현할 수 있게 하여, 기존 매처 조합의 한계를 극복하고 매우 유연하고 명확하며 강력한 테스트 코드를 작성할 수 있도록 합니다. satisfy 매처는 테스트 로직의 명확성을 높이고, 특정 비즈니스 로직에 특화된 복잡한 검증을 RSpec 테스트 내에 자연스럽게 통합할 수 있는 길을 열어줍니다.