Strange "Expected invocation on the mock at least once, but was never performed" error when I am setting up the Mock - vb.net

I'm getting this error from Moq via NUnit, and it doesn't make much in the way of sense to me.
"Expected invocation on the mock at least once, but was never performed: x => x.DeleteItem(.$VB$Local_item)"
"at Moq.Mock.ThrowVerifyException(MethodCall expected, IEnumerable1 setups, IEnumerable1 actualCalls, Expression expression, Times times, Int32 callCount)
at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
at Moq.Mock.Verify[T](Mock mock, Expression1 expression, Times times, String failMessage)
at Moq.Mock1.Verify(Expression`1 expression)
at PeekABookEditor.UnitTests.ItemBrowsing.Can_Delete_Item() in C:\Projects\MyProject\MyProject.UnitTests\Tests\ItemBrowsing.vb:line 167"
Very similar code works well in C#, so the error might be minor and syntactical on my part.
Here's my code:
<Test()> _
Public Sub Can_Delete_Item()
'Arrange: Given a repository containing some item...
Dim mockRepository = New Mock(Of IItemsRepository)()
Dim item As New Item With {.ItemID = "24", .Title = "i24"}
mockRepository.Setup(Function(x) x.Items).Returns(New Item() {item}.AsQueryable())
'Act ... when the user tries to delete that product
Dim controller = New ItemsController(mockRepository.Object)
Dim result = controller.Delete(24)
'Assert ... then it's deleted, and the user sees a confirmation
mockRepository.Verify(Sub(x) x.DeleteItem(item))
result.ShouldBeRedirectionTo(New With {Key .action = "List"})
Assert.AreEqual(DirectCast(controller.TempData("message"), String), "i24 was deleted")
End Sub
The guilty line appears to be "mockRepository.Verify(Sub(x) x.DeleteItem(item))"
Any thoughts on how to fix this?
Working C# code isn't the exact same, but here it is:
[Test]
public void Can_Delete_Product()
{
// Arrange: Given a repository containing some product...
var mockRepository = new Mock<IProductsRepository>();
var product = new Product { ProductID = 24, Name = "P24"};
mockRepository.Setup(x => x.Products).Returns(
new[] { product }.AsQueryable()
);
// Act: ... when the user tries to delete that product
var controller = new AdminController(mockRepository.Object);
var result = controller.Delete(24);
// Assert: ... then it's deleted, and the user sees a confirmation
mockRepository.Verify(x => x.DeleteProduct(product));
result.ShouldBeRedirectionTo(new { action = "Index" });
controller.TempData["message"].ShouldEqual("P24 was deleted");
}

In your VB test method, you create Item with a string ItemID = "24", but you call the controller.Delete method with an integer value of 24.
Check your controller code and see if the type discrepancy results in the item not being identified correctly, so either DeleteItem is not called at all, or is called with a different Item.

Related

Getting original object from Entity Framework SaveChanges function?

For auditing reasons I am overriding the SaveChanges function. However, I want to capture to original and current values as the original object (i.e person) so that I can serialize both the before and after.
Public Overrides Function SaveChanges() As Integer
ChangeTracker.DetectChanges()
Dim ctx As ObjectContext = DirectCast(Me, IObjectContextAdapter).ObjectContext
Dim objectStateEntryList As List(Of ObjectStateEntry) = ctx.ObjectStateManager.
GetObjectStateEntries(EntityState.Added Or EntityState.Modified Or EntityState.Deleted).ToList()
For Each ent As ObjectStateEntry In objectStateEntryList
If Not ent.IsRelationship Then
Dim objectType As Type = ObjectContext.GetObjectType(ent.Entity.GetType)
Dim audit As New Audit With {
.ObjectId = ent.EntityKey.EntityKeyValues.First.Value,
.ObjectType = ObjectContext.GetObjectType(ent.Entity.GetType).Name,
.User = (From u In Users Where u.Username = My.User.Name).First
}
With audit
Select Case ent.State
Case EntityState.Added
.Action = "Created"
.Detail = "Record created"
Case EntityState.Deleted
.Action = "Deleted"
.Detail = "Record deleted"
Case EntityState.Modified
Dim newObj As String = SerializeToString(
Convert.ChangeType(ent.Entity, objectType)
)
.Action = "Modified"
.Detail = newObj.ToString
End Select
End With
End If
Next
Return MyBase.SaveChanges()
End Function
That's how far I got, but when I try and ChangeType it throws "Object must implement IConvertible".
The last time I worked on a project that absolutely needed to track every change, we used a history table and an ON UPDATE trigger. Changing data would fire the trigger, which would then copy the original row into the history table.
This is 100% EF compatible, but you need to set it up separately for each table.
This doesn't exactly answer your main question but may help you to improve your auditing.
Disclaimer: I'm the owner of the project Entity Framework Plus
I recommend you to look at our EF+ Audit Feature, all auditing information can be easily retrieved using this library.
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntityContext();
// ... ctx changes ...
var audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.SaveChanges(audit);
// Access to all auditing information
var entries = audit.Entries;
foreach(var entry in entries)
{
foreach(var property in entry.Properties)
{
}
}
// CALL your serializer here
SerializeToString(entries, ...);
The code is Open Source.

Translation of c# linq to vb - overload resolution failure in intellisense with 'selectmany' statement

I've tried every translation service under the sun to get this syntax right but I still get "Overload resolution failed because no accessible 'SelectMany' can be called with these arguments"
on the first part of the select statement (up to the full stop just before the groupby keyword)
the original c# statement from an online example I'm trying to get working locally:
public IEnumerable<TagGroup> GetTagGroups()
{
var tagGroups =
// extract the delimited tags string and session id from all sessions
DbSet.Select(s => new { s.Tags, s.Id })
.ToArray() // we'll process them in memory.
// split the "Tags" string into individual tags
// and flatten into {tag, id} pairs
.SelectMany(
s =>
s.Tags.Split(_tagDelimiter, StringSplitOptions.RemoveEmptyEntries)
.Select(t => new { Tag = t, s.Id })
)
// group {tag, id} by tag into unique {tag, [session-id-array]}
.GroupBy(g => g.Tag, data => data.Id)
// project the group into TagGroup instances
// ensuring that ids array in each array are unique
.Select(tg => new TagGroup
{
Tag = tg.Key,
Ids = tg.Distinct().ToArray(),
})
.OrderBy(tg => tg.Tag);
return tagGroups;
}
The closest I've come to it in VB:
Public Function GetTagGroups() As IEnumerable(Of TagGroup)
' extract the delimited tags string and session id from all sessions
' we'll process them in memory.
' split the "Tags" string into individual tags
' and flatten into {tag, id} pairs
' group {tag, id} by tag into unique {tag, [session-id-array]}
' project the group into TagGroup instances
' ensuring that ids array in each array are unique
Dim tagGroups = DbSet.[Select](Function(s) New With { _
s.Tags, _
s.Id _
}).ToArray().SelectMany(Function(s) s.Tags.Split(_tagDelimiter, StringSplitOptions.RemoveEmptyEntries).[Select](Function(t) New With { _
Key .Tag = t, _
s.Id _
})).GroupBy(Function(g) g.Tag, Function(data) data.Id).[Select](Function(tg) New With { _
Key .Tag = tg.Key, _
Key .Ids = tg.Distinct().ToArray() _
}).OrderBy(Function(tg) tg.Tag)
Return tagGroups
End Function
This results in the visual studio 2012 intellisense underlining in blue the first part of the statement from "DbSet" on the first line through to the last parenthesis before the ".GroupBy" near the bottom. The error is "Overload resolution failed because no accessible 'SelectMany' can be called with these arguments".
As it's a code example I'm trying to convert to vb to run locally and understand and I'm not experienced enough with linq I'm completely at a loss of how to try and deal with this. It's way beyond my current understanding so could be a simple syntax error or a complete hash from start to finish for all I know!
Would be very grateful for any pointers.
I have now put this in VS2k8, and as well as the two simple issues with the VB sample I previously noted:
The first two New With constructs must specify .<field> = AFAIK, and
the last New With should not be anonymous.
I now also note these are the declarations I needed to get the code to not have errors. Other than adding intermediate query variables (which BTW mean you could thread the C# comments back in), I don't recall actually changing the code further. Note the _tagDelimiter as a Char() -- what did the C# code declare it as? (Looking at the String.Split overloads that mention StringSplitOptions it has to be Char() or String() or C# is implicitly changing types somewhere that VB.NET doesn't.)
Class TagList
Public Tags As String
Public Id As String
End Class
Private DbSet As IQueryable(Of TagList)
Class TagGroup
Public Tag As String
Public Ids() As String
End Class
Private _tagDelimiter As Char()
Public Function GetTagGroups() As IEnumerable(Of TagGroup)
Dim theTags = DbSet.[Select](Function(s) New With { _
.Tags = s.Tags, _
.Id = s.Id _
}).ToArray()
Dim many = theTags.SelectMany(Function(s) s.Tags.Split(_tagDelimiter, StringSplitOptions.RemoveEmptyEntries).[Select](Function(t) New With { _
Key .Tag = t, _
.Id = s.Id _
}))
Dim grouped = many.GroupBy(Function(g) g.Tag, Function(data) data.Id)
Dim unordered = grouped.[Select](Function(tg) New TagGroup With { _
.Tag = tg.Key, _
.Ids = tg.Distinct().ToArray() _
})
Dim tagGroups = unordered.OrderBy(Function(tg) tg.Tag)
Return tagGroups
End Function

When getting types from an assembly, is there a way to determine if type is an Anonymous Type?

I added an Anonymous type to my project:
'Put the courses from the XML file in an Anonymous Type
Dim courses = From myCourse In xDoc.Descendants("Course")
Select New With
{
.StateCode = myCourse.Element("StateCode").Value, _
.Description = myCourse.Element("Description").Value, _
.ShortName = myCourse.Element("ShortName").Value, _
.LongName = myCourse.Element("LongName").Value, _
.Type = myCourse.Element("Type").Value, _
.CipCode = CType(myCourse.Element("CIPCode"), String) _
}
For Each course In courses
If Not UpdateSDECourseCode(acadYear, course.StateCode, course.Description, course.Type, course.ShortName, course.LongName, course.CipCode) Then errors.Add(String.Format("Cannot import State Course Number {0} with Year {1} ", course.StateCode, acadYear))
Next
After doing so, a Unit Test failed:
Public Function GetAreaTypeList() As List(Of Type)
Dim types As New List(Of Type)
Dim asmPath As String = IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "My.Stuff.dll")
For Each t As Type In Reflection.Assembly.LoadFrom(asmPath).GetTypes()
If t.Namespace.StartsWith("My.Stuff.BatchUpdater") Then
If t.BaseType Is GetType(My.Stuff.BatchUpdater.Area) Then
types.Add(t)
End If
End If
Next
Return types
End Function
It fails because a new type has been added to the project (VB$AnonymousType_0`6) and it does not have a property called Namespace.
I fixed by making the following change to the IF Statement:
If Not t.Namespace Is Nothing AndAlso t.Namespace.StartsWith("My.Stuff.BatchUpdater") Then
Since I don't fully understand what's happening, I feel leery about my code change.
Why is the Namespace Nothing for the Anonymous type?
Would you fix your Unit Test in the same fashion? Or should it be something more specific (e.g. If Not t.Names = "VB$AnonymousType_0`6")
UPDATE
decyclone gave me the info I needed to create a better test:
For Each t As Type In Reflection.Assembly.LoadFrom(asmPath).GetTypes()
'Ignore CompilerGeneratedAttributes (e.g. Anonymous Types)
Dim isCompilerGeneratedAttribute = t.GetCustomAttributes(False).Contains(New System.Runtime.CompilerServices.CompilerGeneratedAttribute())
If Not isCompilerGeneratedAttribute AndAlso t.Namespace.StartsWith("My.Stuff.BatchUpdater") Then
'...Do some things here
End If
Next
Honestly, it could be improved more with a LINQ query, but this suits me.
Anonymous methods and types are decorated with CompilerGeneratedAttribute. You can check for their existance to identify an anonymous type.
var anonymous = new { value = 1 };
Type anonymousType = anonymous.GetType();
var attributes = anonymousType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
if (attributes.Any())
{
// Anonymous
}
You can filter these out in your test.
It is also possible to mark a user defined type with CompilerGeneratedAttribute. So maybe you can combine it with checking if Namespace is null or not
var anonymous = new { value = 1 };
Type anonymousType = anonymous.GetType();
var attributes = anonymousType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
if (attributes.Any() && anonymousType.Namespace == null)
{
// Anonymous
}

Index (zero based) must be greater than or... Working with the Bit.ly API

I'm working (actually more like playing) around with the Bit.ly API, and keep getting the error in the title of this question. So I'm going to show you the code and hopefuly someone can help me resolve this. First the client side code.
var x = service.GetClicks(url, service.BitlyLogin, service.BitlyAPIKey);
Console.WriteLine(x);
Console.ReadLine();
And this is the code that's being called
public List<int> GetClicks(string url, string login, string key)
{
List<int> clicks = new List<int>();
url = Uri.EscapeUriString(url);
string reqUri =
String.Format("http://api.bit.ly/v3/clicks?" +
"login={0}&apiKey={1}&shortUrl={2}&format=xml" +
login, key, url);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
req.Timeout = 10000; // 10 seconds
Stream stm = req.GetResponse().GetResponseStream();
XmlDocument doc = new XmlDocument();
doc.Load(stm);
// error checking for xml
if (doc["response"]["status_code"].InnerText != "200")
throw new WebException(doc["response"]["status_txt"].InnerText);
XmlElement el = doc["response"]["data"]["clicks"];
clicks.Add(int.Parse(el["global_clicks"].InnerText));
clicks.Add(int.Parse(el["user_clicks"].InnerText));
return clicks;
}
As you can see it's very simple code, nothing complicated, and I can see nothing that causes this error. Anyone out there who has worked with(the full error is Index (zero based) must be greater than or equal to zero and less than the size of the argument list.) the Bit.ly API and can lend a hand?
Instead this
string reqUri =
String.Format("http://api.bit.ly/v3/clicks?" +
"login={0}&apiKey={1}&shortUrl={2}&format=xml" + login, key, url);
Use this
string reqUri = String.Format("http://api.bit.ly/v3/clicks?login={0}&apiKey={1}&shortUrl={2}&format=xml", login, key, url);
Notice that I just changed the plus sign with the comma before "login, key, url);" at the end of the String.Format().
I narrowed it down to a place where I was using string.Format to build an array and has less in the string.Format than what was supposed to. I had it go to Index 3 but only filled to Index 2
Not for your specific case, but I ran into this: make sure that, if you have multiple parameters, you send them as an array of objects instead of an IEnumerable:
IEnumerable<object> myArgs = ...;
string toFormat = "{0} xyz {1}";
String.Format(toFormat, myArgs);
// ERROR, since myArgs is one argument whereas the string template requires two
String.Format(toFormat, myArgs.ToArray());
// Valid, as the Format() accepts an array of objects to fill all arguments in the string

Get value from SPFieldUser with AllowMultipleValues fails only in a Timer Job

This one is weird.
I'm executing this code in a Timer Job in SharePoint 2010 ...
...
// Get the field by it's internal name
SPField field = item.Fields.GetFieldByInternalName(fieldInternalName);
if (field != null)
{
SPFieldUser userField = (SPFieldUser)field;
object value = null;
if (userField.AllowMultipleValues)
{
// Bug when getting field value in a timer job? Throws an ArgumentException
users = new SPFieldUserValueCollection(item.ParentList.ParentWeb, item[userField.Id].ToString());
}
else
{
// Get the value from the field, no exception
value = item[userField.Id];
}
}
...
This code works perfectly when run in a simple ConsoleApplication but when run in the context of a Timer Job in SharePoint 2010 it throws an ArgumentException in the line ...
users = new SPFieldUserValueCollection(item.ParentList.ParentWeb, item[userField.Id].ToString());
I've tried many variations to retreive a value from a SPFieldUser but all fail only when a Timer Job is executing it and the field has AllowMultipleValues property set to TRUE.
I have tried debugging with Reflector and it seems that the exception is being thrown here in SPListItem ...
public object this[Guid fieldId]
{
get
{
SPField fld = this.Fields[fieldId];
if (fld == null)
{
throw new ArgumentException();
}
return this.GetValue(fld, -1, false);
}
...
And this here would be the exception stack trace...
System.ArgumentException was caught
Message=Value does not fall within the expected range.
Source=Microsoft.SharePoint
StackTrace:
at Microsoft.SharePoint.SPFieldMap.GetColumnNumber(String strFieldName, Boolean bThrow)
at Microsoft.SharePoint.SPListItemCollection.GetColumnNumber(String groupName, Boolean bThrowException)
at Microsoft.SharePoint.SPListItemCollection.GetRawValue(String fieldname, Int32 iIndex, Boolean bThrow)
at Microsoft.SharePoint.SPListItem.GetValue(SPField fld, Int32 columnNumber, Boolean bRaw, Boolean bThrowException)
at Microsoft.SharePoint.SPListItem.get_Item(Guid fieldId)
at FOCAL.Point.Applications.Audits.AuditUtility.GetPeopleFromField(SPListItem item, String fieldInternalName)
Sighh... any thoughts?
This generally means that you have requested too many lookup fields in a single SPQuery which would cause too many self-joins of the true-lookup-table in the content database unless SharePoint Foundation throttled resources. There is a threshold setting that is at 8 lookups per query for ordinary users. Make sure your query only returns the necessary lookup or person/group fields. If you can't decrease the usage, then consider altering the threshold setting.