Gestern twitterte Thomas über ein Problem beim Mocken, nach der Lösung blogte er darüber.
Das Problem ist dass Rhino Mocks bei object.AssertWasCalled() bei der Parameter-Überprüfung auf die Gleichheit eines Objektes geprüft wird und somit wenn man nur auf eine bestimmte Property prüfen will dies nicht geht.
Somit schlug dieser Test fehl.
[TestMethod]
public void ChangeEmail_POST_sendet_eine_Aktivierungs_Email_an_den_Benutzer()
{
// Snipp (Nicht vorhandene Logik des Tests nicht beachten :))
notificationService.AssertWasCalled(
n => n.Send(configurationService.GetValue("Email.Sender"),
new EmailRecipient { To = "abc@efg.hij" });
}
Für die Lösung musste er .Equals() überschreiben, nur für den Test. Desweiteren gibt es auch Probleme wenn man nur einzelne Properties eines Objektes überprüfen will. Gerade bei komplexeren Klassen kommt dies öfters vor.
Hier eine Lösung wo man .Equals() nicht überschreiben muss, und auch auf einzelne Properties überprüfen kann.
Das Beispiel von Thomas würde damit so aussehen.
[TestMethod]
public void ChangeEmail_POST_sendet_eine_Aktivierungs_Email_an_den_Benutzer()
{
// Snipp (Nicht vorhandene Logik des Tests nicht beachten :))
notificationService.AssertWasCalled(
n => n.Send(configurationService.GetValue("Email.Sender"),
Args<EmailRecipient>.MatchProperties( new { To = "abc@efg.hij" });
}
Hiermit wird nur das To-Property des Objekte welches als Parameter übergeben wird überprüft und alle anderen Properties werden ignoriert (sofern man ein anonymes Objekt übergibt). Natürlich kann man auch ein Objekte eines bekannten Typs übergeben, dann müssen jedoch auch alle Properties angegeben werden.
Hier nun der Code dazu.
using System;
using Rhino.Mocks;
using Rhino.Mocks.Constraints;
namespace DerAlbert.UnitTest.Constraints
{
public static class Anonymous
{
public static AbstractConstraint MatchProperties(object expected)
{
return new AnonymousPropertyConstraint(expected);
}
}
public static class Args<T> where T : class
{
public static T MatchProperties(object expected)
{
return Arg<T>.Matches(Anonymous.MatchProperties(expected));
}
}
}
und
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Rhino.Mocks.Constraints;
namespace DerAlbert.UnitTest.Constraints
{
public sealed class AnonymousPropertyConstraint : AbstractConstraint
{
private readonly object expected;
private readonly IList<string> messages = new List<string>();
public AnonymousPropertyConstraint(object expected)
{
if (expected == null)
{
throw new ArgumentNullException("expected");
}
this.expected = expected;
}
public override bool Eval(object current)
{
return EvalExpectations(current);
}
private bool EvalExpectations(object current)
{
var expectedPropertyInfos = expected.GetType().GetProperties();
foreach (var expectedInfo in expectedPropertyInfos)
{
object currentValue;
object expectedValue = GetExpectedValue(expectedInfo);
if (TryGetCurrentValue(expectedInfo, current, out currentValue))
{
if (!Equals(expectedValue, currentValue))
{
messages.Add(string.Format(@"'expected value on [{0}.{1}] should be [{2}] but was [{3}]'",
current.GetType().Name,
expectedInfo.Name,
expectedValue,
currentValue));
}
}
}
return messages.Count == 0;
}
private object GetExpectedValue(PropertyInfo expectedInfo)
{
return expectedInfo.GetValue(expected, new object[0]);
}
private bool TryGetCurrentValue(PropertyInfo expectedInfo, object current, out object value)
{
value = null;
var currentInfo = current.GetType().GetProperty(expectedInfo.Name);
if (currentInfo == null)
{
messages.Add(string.Format(@"'expected property [{0}.{1}] does not exists'", current.GetType().Name, expectedInfo.Name));
return false;
}
value = currentInfo.GetValue(current, new object[0]);
return true;
}
public override string Message
{
get
{
var sb = new StringBuilder();
foreach (var message in messages)
{
sb.AppendLine(message);
}
return sb.ToString();
}
}
}
}
Viel Spaß bei der Anwendung.
Technorati-Tags:
TDD,
Rhino Mocks,
C#,
.NET
Print | posted on Wednesday, May 27, 2009 8:50 AM
Der Eintrag ist mir etwas Wert