Проверьте значение параметра ссылки с Moq
Я только что переключился на Moq и столкнулся с проблемой. Я тестирую метод, который создает новый экземпляр бизнес-объекта, устанавливает свойства объекта из входных значений пользователя и вызывает метод (SaveCustomerContact) для сохранения нового объекта. Бизнес-объект передается в качестве аргумента ref, потому что он проходит через слой удаленного взаимодействия. Мне нужно проверить, что для объекта, передаваемого в SaveCustomerContact, все его свойства установлены так, как ожидалось, но, поскольку он создается как новый в методе контроллера, я не могу этого сделать.
public void AddContact() {
var contact = new CustomerContact() { CustomerId = m_model.CustomerId };
contact.Name = m_model.CustomerContactName;
contact.PhoneNumber = m_model.PhoneNumber;
contact.FaxNumber = m_model.FaxNumber;
contact.Email = m_model.Email;
contact.ReceiveInvoiceFlag = m_model.ReceiveInvoiceFlag;
contact.ReceiveStatementFlag = m_model.ReceiveStatementFlag;
contact.ReceiveContractFlag = m_model.ReceiveContractFlag;
contact.EmailFlag = m_model.EmailFlag;
contact.FaxFlag = m_model.FaxFlag;
contact.PostalMailFlag = m_model.PostalMailFlag;
contact.CustomerLocationId = m_model.CustomerLocationId;
RemotingHandler.SaveCustomerContact( ref contact );
}
Вот тест:
[TestMethod()]
public void AddContactTest() {
int customerId = 0;
string name = "a";
var actual = new CustomerContact();
var expected = new CustomerContact() {
CustomerId = customerId,
Name = name
};
model.Setup( m => m.CustomerId ).Returns( customerId );
model.SetupProperty( m => model.CustomerContactName, name );
model.SetupProperty( m => m.PhoneNumber, string.Empty );
model.SetupProperty( m => m.FaxNumber, string.Empty );
model.SetupProperty( m => m.Email, string.Empty );
model.SetupProperty( m => m.ReceiveInvoiceFlag, false );
model.SetupProperty( m => m.ReceiveStatementFlag, false );
model.SetupProperty( m => m.ReceiveContractFlag, false );
model.SetupProperty( m => m.EmailFlag, false );
model.SetupProperty( m => m.FaxFlag, false );
model.SetupProperty( m => m.PostalMailFlag, false );
model.SetupProperty( m => m.CustomerLocationId, 0 );
remote
.Setup( r => r.SaveCustomerContact( ref actual ) )
.Callback( () => Assert.AreEqual( actual, expected ) );
target.AddContact();
}
Это только последняя из многих попыток получить доступ к этому параметру. Для справки, значение фактического не изменяется от своего начального (построенного) состояния.
Перемещение Assert.AreEqual(ожидаемое, фактическое) после сбоя целевого вызова. Если я добавляю.Verifiable() к настройке вместо.CallBack и затем вызываю remote.Verify после цели (или, я полагаю, устанавливаю для mock строгий режим), он всегда завершается ошибкой, поскольку параметр, который я предоставляю в тесте, не является тот же экземпляр, что и в методе контроллера.
Я использую Moq 3.0.308.2. Любые идеи о том, как проверить это, будут оценены. Спасибо!
4 ответа
Я не могу предложить вам точное решение, но альтернативой было бы скрыть семантику pass-by-ref за адаптером, который принимает параметр по значению и перенаправляет его в RemotingHandler. Это было бы проще для насмешки, и удалило бы "ref" бородавку из интерфейса (я всегда с подозрением отношусь к параметрам ref:-))
РЕДАКТИРОВАТЬ:
Или вы можете использовать заглушку вместо макета, например:
public class StubRemotingHandler : IRemotingHandler
{
public CustomerContact savedContact;
public void SaveCustomerContact(ref CustomerContact contact)
{
savedContact = contact;
}
}
Теперь вы можете проверить сохраненный объект в вашем тесте:
IRemotingHandler remote = new StubRemotingHandler();
...
//pass the stub to your object-under-test
...
target.AddContact();
Assert.AreEqual(expected, remote.savedContact);
Вы также говорите в своем комментарии:
Я не хотел бы начинать прецедент обертывания случайных битов бэкэнда, чтобы мне было легче писать тесты
Я думаю, что это именно тот прецедент, который вам нужно установить! Если ваш код не поддается тестированию, вы будете продолжать пытаться его протестировать. Упростите тестирование и увеличьте охват.
Последняя версия Moq поддерживает этот сценарий.
Взято из краткого руководства по адресу http://code.google.com/p/moq/wiki/QuickStart:
// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
К сожалению, я не уверен, что это возможно без прямой поддержки со стороны Moq. Проблема в том, что лямбда-выражения не поддерживают ref или out.
"Лямбда-выражение не может напрямую захватить параметр ref или out из включающего метода".
http://msdn.microsoft.com/en-us/library/bb397687.aspx
Я не могу даже заставить пример как твой работать. Добавление ref в настройку не компилируется.
Возможно, вы захотите проверить обсуждения Moq для получения дополнительной информации http://groups.google.com/group/moqdisc
Удачи.
Я столкнулся с подобной проблемой. Бит я получил решение, используя последний Moq и передавая значение как
var instance = new Bar(); Mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
Раньше я тоже использовал тот же метод, но не получал возвращение как истинное.
Внутри фактического экземпляра функции создавалось, и перезапись экземпляра, переданного из класса модульного теста, вызывала проблему. Я удалил создание экземпляра внутри фактического класса, и тогда это сработало.
Надеюсь, это поможет вам.
Спасибо