Как я могу проверить, что мой обратный вызов before_save выполняется правильно

#ruby-on-rails #ruby #activerecord #rspec

#ruby-on-rails #ruby #activerecord #rspec

Вопрос:

У меня есть обратный вызов в моей модели ActiveRecord, как показано ниже:

   before_save :sync_to_external_apis

  def sync_to_external_apis                                                                                                 
    [user, assoc_user].each {|cuser|
      if cuser.google_refresh
        display_user = other_user(cuser.id)
        api = Google.new(:user => cuser)
        contact = api.sync_user(display_user)
      end
    }
  end
  

Я хотел бы написать тест rspec, который проверяет этот вызов save! в экземпляре этой модели sync_user вызывается в новом экземпляре Google, когда значение google_refresh равно true. Как я мог это сделать?

Ответ №1:

 it "should sync to external apis on save!" do
  model = Model.new
  model.expects(:sync_to_external_apis)
  model.save!
end
  

Кроме того, запрашивать ненадежные ресурсы, такие как Интернет, во время цикла запрос-ответ — плохая идея. Я бы предложил вместо этого создать фоновое задание.

Комментарии:

1. Спасибо. Это проверяет, вызывается ли sync_to_external_apis вообще. Но как я могу проверить фактическую функциональность этого обратного вызова? Мне нужно проверить, что api.sync_user вызывается при правильных условиях.

Ответ №2:

Обычный метод тестирования — убедиться, что результаты соответствуют ожидаемым. Поскольку в данном случае вы используете API, это может все усложнить. Вы можете обнаружить, что использование mocha для создания макетного объекта, который вы можете отправлять вызовы API, позволит вам заменить Google класс чем-то, что работает так же хорошо для целей тестирования.

Более простой, но неуклюжий подход заключается в переключении «тестового режима»:

 def sync_to_external_apis
  [ user, assoc_user ].each do |cuser|
    if (Rails.env.test?)
      @synced_users ||= [ ]
      @synced_users << cuser
    else
      # ...
    end
  end
end

def did_sync_user?(cuser)
  @synced_users and @synced_users.include?(cuser)
end
  

Это простой подход, но он не будет проверять правильность выполнения ваших вызовов API.

Комментарии:

1. Наличие переключателя в производственном коде для тестирования — ужасная идея. -1

2. Я действительно сказал, что это неуклюжий, неоптимальный подход. Однако, с практической точки зрения, есть некоторые вещи, которые необходимо отключить в тестовой среде, поэтому вам, возможно, придется прибегнуть к подобным вещам, если вы не сможете перепроектировать реализацию, чтобы избежать подобных зависимостей. Наличие переключателя на уровне приложения sync_to_api? , безусловно, является шагом в правильном направлении. Однако такого рода вещи на несколько уровней удалены от исходного вопроса.

3. И вы должны отключить их в тестовой среде. Ваша производственная среда никогда не должна ничего знать о вашей тестовой среде. Когда-либо. TDD поможет предотвратить подобные дизайнерские запахи.

4. Мне не совсем ясно, какой наилучший способ отключить его от тестов. Такое ощущение, что мне нужно использовать другой, возможно, издевательский / заглушенный класс Google из тестов, но не уверен, возможно ли это.

5. Один из способов обойти большую часть этой путаницы — создать оболочку модуля для класса Google, который выполняет синхронизацию за вас с помощью серии вспомогательных методов. Вы могли бы определить метод, подобный Refresher::Google.updateuser , для обработки вызовов, а затем настроить его, чтобы избежать конфликтов в тестовой среде. Это похоже на то, как у ActionMailer есть другой режим для тестирования.

Ответ №3:

Мокко — это правильный путь. Я не знаком с rspec, но вот как вы могли бы сделать это в тестовом модуле:

def test_google_api_gets_called_for_user_and_accoc_user 
 user = mock('Пользователь') # определите макет объекта и пометьте его 'User' 
 accoc_user = mock('AssocUser') # определите макет объекта и пометьте его 'AssocUser'

 # создайте экземпляр тестируемой модели с помощью макетных объектов 
 модель = Model.new(пользователь, assoc_user)

 # отключите метод other_user. Он вернет cuser1, когда макет пользователя 
 # передается в и cuser2 при передаче макета assoc_user 
 cuser1 = mock('Cuser1')
 cuser2 = mock('Cuser2')
 model.expects(:other_user).with(user).returns(пользователь1)
 модель.ожидает(:other_user).с (assoc_user).возвращает(пользователь2)


 # задайте ожидания в Google API 
 api1 - mock('GoogleApiUser1') # определяет макет объекта и помечает его 'GoogleApiUser1'
 api2 - mock('GoogleApiUser2') # определяет макет объекта и помечает его 'GoogleApiUser2'

 # вызовите new в Google, передавая макет пользователя и получая обратно макет объекта Google api
 Google.ожидает(:new).with(:user => cuser1).возвращает (api1)
 api1.ожидает(:sync_user).with(пользователь1)

 Google.ожидает(:new).with(:user => cuser2).возвращает (api2)
 api2.ожидает(:sync_user).with(cuser2)

 # теперь выполните код, который должен удовлетворять всем приведенным выше ожиданиям 
 model.save!
завершение

Вышесказанное может показаться сложным, но это не так, как только вы освоитесь с этим. Вы проверяете, что при вызове save ваша модель выполняет то, что она должна делать, но у вас нет хлопот или временных затрат на реальное взаимодействие с API, создание экземпляров записей базы данных и т.д.