How to setup the same method multiple times with different arguments? - c#

I have an object under test with a method called void Print(string msg).
The object invoke the method multiple times and passes a different message.
For example...
Strait forward of the usage:
public interface IMyPrinter
{
void Print(string msg);
}
public class Printer : IMyPrinter
{
public void Print(string msg)
{
// print to console
}
}
public class MyObject
{
public IMyPrinter Printer { get; set; }
public void foo()
{
for (var count = 0; count < 4; count++)
{
Printer.Print($"abc_{count}");
}
}
}
When I want to test foo method, how can setup the Mock object to capture the different Print methods calls?
I tried:
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
for (var count = 0; count < 4; count++)
{
printerMock.Setup(_ => _.Print($"abc_{count}");
}
var underTest = new MyObject();
underTest.Printer = printerMock.Object;
underTest.foo();
printerMock.VerifyAll();
but this of course made only the last setup effective (when count = 3).
How can this be done?

Another option, since in this case the mocked member has no expected behavior in a loose mock, would be to use the loop for verification instead of setup.
For example
// Arrange
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
var underTest = new MyObject();
underTest.Printer = printerMock.Object;
// Act
underTest.foo();
// Assert
for (int count = 0; count < 4; count++) {
string msg = $"abc_{count}";
printerMock.Verify(_ => _.Print(msg), Times.Once);
}
If you want to capture the actual arguments passed, then a call back can be used
// Arrange
List<string> expected = new List<string>() { ... }; //populated
List<string> actual = new List<string>();
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
printerMock
.Setup(_ => _.Print(It.IsAny<string>()))
.Callback(string msg => actual.Add(msg));
var underTest = new MyObject();
underTest.Printer = printerMock.Object;
// Act
underTest.foo();
// Assert
//..If messages are known before hand then they can be used to assert the
//the captured messages from when the mock was invoked.
actual.Should().BeEquivalentTo(expected); //Using FluentAssertions

but this of course made only the last setup effective (when count = 3).
actually that is not true, if you look closely to the test output you will see that it contains 4 calls with the value of 4!:
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
this is the value that count has after the loop has ended. Since you are using a lambda expression, your count variable is caught in closure and the last value 4 was captured. Only this value is used when the lambda expression is evaluated later on in your code when the loop has finished a long time ago. You need to create a temporaty variable and capture the index in it. And your test will run green:
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
for (var count = 0; count < 4; count++)
{
int i = count; // <-- this is the main change
printerMock.Setup(_ => _.Print($"abc_{i}")); // <-- use "i" in lambda
}
var underTest = new MyObject();
underTest.Printer = printerMock.Object;
underTest.foo();
printerMock.VerifyAll();
EDIT:
for further reading on closures I recommend this article by Jon Skeet

Related

How can I access multi-element List data stored in a public class?

My first question on SO:
I created this public class, so that I can store three elements in a list:
public class myMultiElementList
{
public string Role {get;set;}
public string Country {get;set;}
public int Commonality {get;set;}
}
In my main class, I then created a new list using this process:
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
I've added data to EmployeeRolesCountry and have validated that has 472 lines. However, when I try to retrieve it as below, my ForEach loop only retrieves the final line added to the list, 472 times...
foreach (myMultiElementList tmpClass in EmployeeRolesCountry)
{
string d1Value = tmpClass.Role;
Console.WriteLine(d1Value);
string d2Value = tmpClass.Role;
Console.WriteLine(d2Value);
int d3Value = tmpClass.Commonality;
Console.WriteLine(d3Value);
}
This was the most promising of the potential solutions I found on here, so any pointers greatly appreciated.
EDIT: adding data to EmployeeRolesCountry
/*
Before this starts, data is taken in via a csvReader function and parsed
All of the process below is concerned with two fields in the csv
One is simply the Country. No processing necessary
The other is bio, and this itself needs to be parsed and cleansed several times to take roles out
To keep things making sense, I've taken much of the cleansing out
*/
private void File_upload_Click(object sender, EventArgs e)
{
int pos = 0;
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
int a = 0;
delimiter = ".";
string token;
foreach (var line in records.Take(100))
{
var fields = line.ToList();
string bio = fields[5];
string country = fields[4];
int role_count = Regex.Matches(bio, delimiter).Count;
a = bio.Length;
for (var i = 0; i < role_count; i++)
{
//here I take first role, by parsing on delimiter, then push back EmployeeRolesCountry with result
pos = bio.IndexOf('.');
if (pos != -1)
{
token = bio.Substring(0, pos);
string original_token = token;
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
a = original_token.Length;
bio = bio.Remove(0, a + 1);
}
}
}
}
EDIT:
When grouped by multiple properties, this is how we iterate through the grouped items:
var employeesGroupedByRolwAndCountry = EmployeeRolesCountry.GroupBy(x => new { x.Role, x.Country });
employeesGroupedByRolwAndCountry.ToList().ForEach
(
(countryAndRole) =>
{
Console.WriteLine("Group {0}/{1}", countryAndRole.Key.Country, countryAndRole.Key.Role);
countryAndRole.ToList().ForEach
(
(multiElement) => Console.WriteLine(" : {0}", multiElement.Commonality)
);
}
);
__ ORIGINAL POST __
You are instantiating rc1 only once (outside the loop) and add the same instance to the list.
Please make sure that you do
var rc1 = new myMultiElementList();
inside the loop where you are adding the elements, and not outside.
All references are the same in your case:
var obj = new myObj();
for(i = 0; i < 5; i++)
{
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
now the list has 5 elements, all pointing to the obj (the same instance, the same object in memory), and when you do
obj.Prop1 = "Prop" + 5
you update the same memory address, and all the pointers in the list points to the same instance so, you are not getting 472 copies of the LAST item, but getting the same instance 472 times.
The solution is simple. Create a new instance every time you add to your list:
for(i = 0; i < 5; i++)
{
var obj = new myObj();
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
Hope this helps.

How to find the placement of a List within another List?

I am working with two lists. The first contains a large sequence of strings. The second contains a smaller list of strings. I need to find where the second list exists in the first list.
I worked with enumeration, and due to the large size of the data, this is very slow, I was hoping for a faster way.
List<string> first = new List<string>() { "AAA","BBB","CCC","DDD","EEE","FFF" };
List<string> second = new List<string>() { "CCC","DDD","EEE" };
int x = SomeMagic(first,second);
And I would need x to = 2.
Ok, here is my variant with old-good-for-each-loop:
private int SomeMagic(IEnumerable<string> source, IEnumerable<string> target)
{
/* Some obvious checks for `source` and `target` lenght / nullity are ommited */
// searched pattern
var pattern = target.ToArray();
// candidates in form `candidate index` -> `checked length`
var candidates = new Dictionary<int, int>();
// iteration index
var index = 0;
// so, lets the magic begin
foreach (var value in source)
{
// check candidates
foreach (var candidate in candidates.Keys.ToArray()) // <- we are going to change this collection
{
var checkedLength = candidates[candidate];
if (value == pattern[checkedLength]) // <- here `checkedLength` is used in sense `nextPositionToCheck`
{
// candidate has match next value
checkedLength += 1;
// check if we are done here
if (checkedLength == pattern.Length) return candidate; // <- exit point
candidates[candidate] = checkedLength;
}
else
// candidate has failed
candidates.Remove(candidate);
}
// check for new candidate
if (value == pattern[0])
candidates.Add(index, 1);
index++;
}
// we did everything we could
return -1;
}
We use dictionary of candidates to handle situations like:
var first = new List<string> { "AAA","BBB","CCC","CCC","CCC","CCC","EEE","FFF" };
var second = new List<string> { "CCC","CCC","CCC","EEE" };
If you are willing to use MoreLinq then consider using Window:
var windows = first.Window(second.Count);
var result = windows
.Select((subset, index) => new { subset, index = (int?)index })
.Where(z => Enumerable.SequenceEqual(second, z.subset))
.Select(z => z.index)
.FirstOrDefault();
Console.WriteLine(result);
Console.ReadLine();
Window will allow you to look at 'slices' of the data in chunks (based on the length of your second list). Then SequenceEqual can be used to see if the slice is equal to second. If it is, the index can be returned. If it doesn't find a match, null will be returned.
Implemented SomeMagic method as below, this will return -1 if no match found, else it will return the index of start element in first list.
private int SomeMagic(List<string> first, List<string> second)
{
if (first.Count < second.Count)
{
return -1;
}
for (int i = 0; i <= first.Count - second.Count; i++)
{
List<string> partialFirst = first.GetRange(i, second.Count);
if (Enumerable.SequenceEqual(partialFirst, second))
return i;
}
return -1;
}
you can use intersect extension method using the namepace System.Linq
var CommonList = Listfirst.Intersect(Listsecond)

Can only enumerate once over IEnumerable

Given is the following code (a xUnit test):
[Fact]
public void SetFilePathTest()
{
// Arrange
IBlobRepository blobRepository = null;
IEnumerable<Photo> photos = new List<Photo>()
{
new Photo()
{
File = "1.jpg"
},
new Photo()
{
File = "1.jpg"
}
};
IEnumerable<CloudBlockBlob> blobs = new List<CloudBlockBlob>()
{
new CloudBlockBlob(new Uri("https://blabla.net/media/photos/1.jpg")),
new CloudBlockBlob(new Uri("https://blabla.net/media/photos/2.jpg"))
};
// Act
photos = blobRepository.SetFilePath2(photos, blobs);
// Assert
Assert.Equal(2, photos.Count());
Assert.Equal(2, photos.Count());
}
Here is the SetFilePath2 method:
public static IEnumerable<T> SetFilePath2<T>(this IBlobRepository blobRepository, IEnumerable<T> entities, IEnumerable<CloudBlockBlob> blobs) where T : BlobEntityBase
{
var firstBlob = blobs.FirstOrDefault();
if (firstBlob is null == false)
{
var prefixLength = firstBlob.Parent.Prefix.Length;
return entities
.Join(blobs, x => x.File, y => y.Name.Substring(prefixLength), (entity, blob) => (entity, blob))
.Select(x =>
{
x.entity.File = x.blob.Uri.AbsoluteUri;
return x.entity;
});
}
else
{
return Enumerable.Empty<T>();
}
}
As you can see, I assert 2 times the very same thing. But only the first assert succeeds. When I step through with the debugger then I can only enumerate the collection once. So at the second Assert it yields no items back.
Can anyone explain me why that happens? I really don't see any problem with this code than explains this behavior.
Every time you call .Count() you basically call
blobRepository.SetFilePath2(photos, blobs).Count() and you modify the entity while using Select.I would recommend using new in the Select statement if you don't mean to alter the original value. That's why you get different results.

index out of range in For loop despite having enough items in array

I was trying to test this case of resusing the loop variable in foreach in c# as mentioned in following question
Is there a reason for C#'s reuse of the variable in a foreach?
After going through this and this, i tried to reproduce this error with for loop as foreach part has been fixed in c# releases.
But to my surprise, when i tried this code, I got an "Index was outside the bounds of the array." exception from code. Although my array has 4 items and i am trying to access the 3rd index.
public static void Main()
{
var strings = new string[] { "sd2", "dafs3", "dasd5", "fdf6" };
var actions = CreateActions(strings);
actions.ForEach(f => f());
}
private static List<Action> CreateActions(string[] strings)
{
var actions = new List<Action>();
for (int i = 0; i < 4; i++)
{
Console.WriteLine(i);
var fiber = strings[i];
actions.Add(() => Console.WriteLine(strings[i]));
}
return actions;
}
Then I changed my code like this
public static void Main()
{
var strings = new string[] { "sd2", "dafs3", "dasd5", "fdf6" };
var actions = CreateActions(strings);
actions.ForEach(f => f());
}
private static List<Action> CreateActions(string[] strings)
{
var actions = new List<Action>();
for (int i = 0; i < 4; i++)
{
Console.WriteLine(i);
var fiber = strings[i];
actions.Add(() => Console.WriteLine(fiber));
}
return actions;
}
This code is running fine and i got no out of range exception which is strange. Also regarding reusing variable my case was proved.
My first code has this output if run upto index 2 in for loop as for index 3 it throw out of range exception.
0
1
2
fdf6
fdf6
fdf6
My second code piece gave this output and list all 4 items in output
0
1
2
3
sd2
dafs3
dasd5
fdf6
Is there any explanation with c# or any issue with my test code.

Pass multiple parameters to a task

I wish to pass two BlockingCollection<>s to a task. I tried to put them in an object array and pass them but it doesn't work. Can anyone help me with this? The code where i am trying to pass the values is written below:
var lineHolders = new[]
{
new BlockingCollection<string>(linesCapacity),
new BlockingCollection<string>(linesCapacity),
new BlockingCollection<string>(linesCapacity),
new BlockingCollection<string>(linesCapacity)
};
var chunksHolder = new[]
{
new BlockingCollection<List<BsonDocument>>(chunksCapacity),
new BlockingCollection<List<BsonDocument>>(chunksCapacity)
};
for (var processors = 0; processors < 16; processors++)
{
var myLineHolder = lineHolders[processors%lineHolders.Length];
var myChunkHolder = chunksHolder[processors%chunksHolder.Length];
processorTaskArray[processors] = Task.Factory.StartNew((arg) =>
{
var lines = (BlockingCollection<string>) arg[0]; // compiler generates error here
var chunks = (BlockingCollection<List<BsonDocument>>) arg[1]; // compiler generates error here
// perform my work...
},
new object []
{
myLineHolder,
myChunkHolder
});
}
You're using the following overload of StartNew:
public Task StartNew(
Action<Object> action,
Object state
)
Since it's just an object you can't apply indexing on it. Cast it and it will work fine.
for (var processors = 0; processors < 16; processors++)
{
var myLineHolder = lineHolders[processors % lineHolders.Length];
var myChunkHolder = chunksHolder[processors % chunksHolder.Length];
processorTaskArray[processors] = Task.Factory.StartNew((arg) =>
{
var properArg = (object[]) arg;
var lines = (BlockingCollection<string>) properArg[0]; // compiler generates error here
var chunks = (BlockingCollection<List<BsonDocument>>) properArg[1]; // compiler generates error here
// perform my work...
},
new object[]
{
myLineHolder,
myChunkHolder
});
}

Categories