I'm trying to write a unit test for a method which looks like this:
public int Save(IEnumerable<int> addedIds, IEnumerable<int> removedIds)
{
var existingIds = repository.Get();
IEnumerable<int> ids = existingIds.Except(removedIds).Union(addedIds));
return repository.Create(ids);
}
The test in Moq looks like this:
repository.Setup(r => r.Get()).Returns(CreateList());
service.Save(addedIds, removedIds);
repository.Verify(r => r.Create(It.Is<IEnumerable<int>>(l => VerifyList(l))));
This fails, with this error, and VerifyList() is never called:
Expected invocation on the mock at least once, but was never
performed:
r => r.Create(It.Is<IEnumerable'1>(list => VerifyList(list)))
Performed invocations:
IRepo.Create(System.Linq.Enumerable+<UnionIterator>d__88'1[System.Int32])
As the invoked type is not IEnumerable<int> but is in fact System.Linq.Enumerable+<UnionIterator>d__88'1[System.Int32]), the test fails. (Stepping through the test, everything is happening correctly and the results are as expected)
If I call ids.ToList() in the method under test, these are the results:
Expected invocation on the mock at least once, but was never performed:
r => r.Create(It.Is<List'1>(l => VerifyList(l)))
Performed invocations:
IRepo.Create(System.Collections.Generic.List'1[System.Int32])
Is there any way round this? Or am I doing something wrong?
Edit: it turns out I had a mistake in my VerifyList method so it was returning false, but Moq wasn't giving me that information. The type difference is a red herring..
This seems to work. Made some assumptions though. Guess the VerifyList method could be better. =)
[Test]
public void Test()
{
// SETUP
Mock<IRepository> repository = new Mock<IRepository>();
Service service = new Service(repository.Object);
repository.Setup(r => r.Get()).Returns(CreateList());
IEnumerable<int> addedIds = new[]{1,2};
IEnumerable<int> removedIds = new[]{3,4};
service.Save(addedIds, removedIds);
repository.Verify(r => r.Create(It.Is<IEnumerable<int>>(l => VerifyList(l))));
}
private static bool VerifyList(IEnumerable<int> enumerable)
{
return enumerable.Contains(1) && enumerable.Contains(2) && enumerable.Contains(5);
}
private IEnumerable<int> CreateList()
{
return new[] { 3, 4, 5 };
}
public interface IRepository
{
IEnumerable<int> Get();
int Create(IEnumerable<int> id);
}
public class Service
{
public Service(IRepository repository)
{
this.repository = repository;
}
private IRepository repository;
public int Save(IEnumerable<int> addedIds, IEnumerable<int> removedIds)
{
var existingIds = repository.Get();
IEnumerable<int> ids = existingIds.Except(removedIds).Union(addedIds);
return repository.Create(ids);
}
You can do:
var another = new List<int> { 1 , 2, 3 };
repository.Verify(r => r.Create(It.Is<IEnumerable<int>>(l => l.SequenceEqual(another)));
Something quick and dirty -
public interface IBlah
{
void Sum(IEnumerable<int> baz);
}
class Blah : IBlah
{
public void Sum(IEnumerable<int> baz)
{
return;
}
}
public class Baz
{
private readonly IBlah blah;
public Baz(IBlah blah)
{
this.blah = blah;
}
public void Sum(IEnumerable<int> baz)
{
blah.Sum(baz);
}
}
And Test it like -
[Test]
public void foo()
{
var mock = new Mock<IBlah>();
var enumerable = Enumerable.Range(1, 10);
var baz = new Baz(mock.Object);
baz.Sum(enumerable.Where(x => x%2 == 0));
mock.Verify(p => p.Sum(It.Is<IEnumerable<int>>(z => z.All(x => x%2==0))));
}
Related
I need to verify that a method has been invoked 2 times with specific arguments:
public void Execute()
{
Counter с = new Counter(1);
// Basically, I need to verify that DoSomething method was called twice:
// First time with an argument, which internal value is 1...
_subject.DoSomething(с);
c.Increment();
// ... and the second time - with internal value of 2
_subject.DoSomething(c);
}
Here is my verification part, with both assertions failing:
// Fails with message "Expected invocation on the mock once, but was 0 times"
mock.Verify(
m => m.DoSomething(
It.Is<Counter>(
x => x.GetValue() == 1
)
),
Times.Once
);
// Fails with message "Expected invocation on the mock once, but was 2 times"
mock.Verify(
m => m.DoSomething(
It.Is<Counter>(
x => x.GetValue() == 2
)
),
Times.Once
);
So the problem is that both invocations inside the Execute method use the same argument, which by the time of performing the assertion contains 2 as its internal value.
The question is how to make the mock to "record" the arguments somehow, so that they are evaluated correctly?
Below is the full listing:
[Test]
public void CheckSomething()
{
Mock<Subject> mock = new Mock<ISubject>();
mock.Setup(m => m.DoSomething(It.IsAny<Counter>()));
var myService = new MyService(mock.Object);
myService.Execute();
mock.Verify(m => m.DoSomething(It.Is<Counter>(x => x.GetValue() == 2)), Times.Once);
mock.Verify(m => m.DoSomething(It.Is<Counter>(x => x.GetValue() == 1)), Times.Once);
}
public interface ISubject
{
public void DoSomething(Counter с);
}
public class Subject : ISubject
{
public void DoSomething(Counter с)
{
// doesn't matter
}
}
public class MyService
{
private readonly ISubject _subject;
public MyService(ISubject subject)
{
_subject = subject;
}
public void Execute()
{
Counter с = new Counter(1);
_subject.DoSomething(с);
с.Increment();
_subject.DoSomething(с);
}
}
public class Counter
{
private int _val;
public Counter(int val)
{
_val = val;
}
public void Increment()
{
_val++;
}
public int GetValue()
{
return _val;
}
}
I really appreciate any feedback, thanks!
You can set a callback when setting up your DoSomething to track the increment of counter's value.
int counterEqual1 = 0;
int counterEqual2 = 0;
mock.Setup(m => m.DoSomething(It.IsAny<Counter>()))
.Callback<Counter>(counter =>
{
if (counter.GetValue() == 1)
counterEqual1++;
else if (counter.GetValue() == 2)
counterEqual2++;
});
Then verify those calls:
mock.Verify(m => m.DoSomething(It.IsAny<Counter>()), Times.Exactly(2));
Assert.That(counterEqual1, Is.EqualTo(1));
Assert.That(counterEqual2, Is.EqualTo(1));
I've been trying to unit test this simple method:
public void DeleteAllSettingsLinkedToSoftware(Guid softwareId)
{
_dbContext.Settings.Where(s => s.SoftwareId == softwareId).ForEachAsync(s => s.IsDeleted = true);
_dbContext.SaveChanges();
}
However I'm having a hard time unit testing this method from the moment the ForEachAsync() method gets called.
So far I've used Moq to setup the dbContext to return the proper settings when the Where() is executed.
My attempt:
Setup(m => m.ForEachAsync(It.IsAny<Action<Setting>>(), CancellationToken.None));
My question is: How will I unit test the call to the ForEachAsync() method?
I've read online that some people say it's impossible to unit test some static methods, if that's true in my case I'm curious about alternatives to test as much of this method as possible.
Edit
My complete test code:
[TestMethod]
public async Task DeleteAllSettingsLinkedToSoftware_Success()
{
//Arrange
var settings = new List<Setting>
{
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId2
}
}.AsQueryable();
var queryableMockDbSet = GetQueryableMockDbSet(settings.ToList());
queryableMockDbSet.As<IQueryable<Setting>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Setting>(settings.Provider));
DbContext.Setup(m => m.Settings).Returns(queryableMockDbSet.Object);
_settingData = new SettingData(DbContext.Object, SettingDataLoggerMock.Object);
//Act
var result = await _settingData.DeleteAllSettingsLinkedToSoftwareAsync(SoftwareId1);
//Assert
DbContext.Verify(m => m.Settings);
DbContext.Verify(m => m.SaveChanges());
Assert.AreEqual(4, DbContext.Object.Settings.Count());
Assert.AreEqual(SoftwareId2, DbContext.Object.Settings.First().SoftwareId);
}
I am aware that my Assert still needs more checks.
The GetQueryableMockDbSet method:
public static Mock<DbSet<T>> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(s => sourceList.Add(s));
dbSet.Setup(d => d.AddRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(sourceList.AddRange);
dbSet.Setup(d => d.Remove(It.IsAny<T>())).Callback<T>(s => sourceList.Remove(s));
dbSet.Setup(d => d.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(s =>
{
foreach (var t in s.ToList())
{
sourceList.Remove(t);
}
});
return dbSet;
}
You don't have to mock ForEachAsync at all. ForEachAsync returns Task and is being execute asynchronously this is the source of your problem.
Use async and await keywards to solve your problem:
public async void DeleteAllSettingsLinkedToSoftware(Guid softwareId)
{
await _dbContext.Settings.Where(s => s.SoftwareId == softwareId)
.ForEachAsync(s => s.IsDeleted = true);
_dbContext.SaveChanges();
}
Edit:
The new exception occurs because the supplied Provider is not a IDbAsyncQueryProvider.
Microsoft implemented a generic version of this interface: TestDbAsyncQueryProvider<TEntity>. Here is the implementation from the link:
internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestDbAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public TestDbAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
Now in the Setup you'll have to use it like:
mockSet.As<IQueryable<Setting>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Setting>(data.Provider));
Disclaimer: I won't provide a direct solution, as requested by OP because I believe this question is a XY problem. Instead I will focus my answer on why is this code so damn hard to test, because yes, more than 30 lines of "arrange" to test 2 lines of code means that something went very wrong.
Short answer
This method doesn't need to be tested, at least at the unit level.
Long answer
The problem with the current implementation is a mix of concern.
The first line: _dbContext.Settings.Where(s => s.SoftwareId == softwareId).ForEachAsync(s => s.IsDeleted = true); contains business logic (s.softwareId == softwareId, s.IsDeleted = true) but also EF logic (_dbContext, ForEachAsync).
The second line: _dbContext.SaveChanges(); contains only EF logic
The point is: such methods (that mix concerns) are hard to test at the unit level. Hence the fact you need mocks and several dozens of "Arrange" code to test only 2 lines of implementation !
Based on this constatation you have 2 options:
You delete your test because the method is mainly containing EF logic which is useless to test (see this great series of articles for more details)
You extract business logic and write a real (and simple) unit test
In the second case, I would implement this logic so that I would be able to write a test like that:
[Test]
public void ItShouldMarkCorrespondingSettingsAsDeleted()
{
var setting1 = new Setting(guid1);
var setting2 = new Setting(guid2);
var settings = new Settings(new[] { setting1, setting2 });
settings.DeleteAllSettingsLinkedToSoftware(guid1);
Assert.That(setting1.IsDeleted, Is.True);
Assert.That(setting1.IsDeleted, Is.False);
}
Easy to write, easy to read.
How about the implementation now ?
public interface ISettings
{
void DeleteAllSettingsLinkedToSoftware(Guid softwareId);
}
public sealed class Settings : ISettings
{
private readonly IEnumerable<Setting> _settings;
public Settings(IEnumerable<Setting> settings) => _settings = settings;
public override void DeleteAllSettingsLinkedToSoftware(Guid softwareGuid)
{
foreach(var setting in _settings.Where(s => s.SoftwareId == softwareId))
{
setting.IsDeleted = true;
}
}
}
public sealed class EFSettings : ISettings
{
private readonly ISettings _source;
private readonly DBContext _dbContext;
public EFSettings(DBContext dbContext)
{
_dbContext = dbContext;
_source = new Settings(_dbContext.Settings);
}
public override void DeleteAllSettingsLinkedToSoftware(Guid softwareGuid)
{
_source.DeleteAllSettingsLinkedToSoftware(softwareGuid);
_dbContext.SaveChanges();
}
}
With a solution like this, each concern is separated which allows to:
Get rid of mocks
Really unit test business-logic code
Gain in maintainability and readability
Hi I am trying to mock the following thing:
var result = _scope.Execute<FooService, IList<FooEntity>>(x => x.GetFooEntities(fooModel));
This is how I try to mock it:
_mockedScope
.Setup(x => x.Execute<FooService, IList<FooEntity>>(f => f.GetFooEntities(It.IsAny<FooModel>())))
.Returns(new List<FooEntity>)
But when I run the test it throws me an exception
Unsupported expression: s => s.GetFooEntities(IsAny())
Any suggestions how can I mock it?
Here is an example what i want to moq
public class Test
{
private readonly IScope _scope;
public Test(IScope scope)
{
_scope = scope;
}
public void Foo()
{
var foo = new FooEntity();
Result<IList<Foo>> result =
_scope.Execute<FooService, IList<Foo>>(
"f",
s => s.GetFoo(foo));
}
}
public class Foo
{
}
public class FooEntity
{
}
public class FooService
{
public List<Foo> GetFoo(FooEntity f);
}
public interface IScope
{
Result<TResult> Execute<T1, TResult>(string temp, Func<T1, TResult> function);
}
public class Result<T>
{
private Result(T value, Exception exception)
{
Value = value;
Error = exception;
}
public Exception Error { get; }
public T Value { get; }
public bool HasError => Error != null;
public static Result<T> Fail(Exception exception) => new Result<T>(default(T), exception);
public static Result<T> Success(T value) => new Result<T>(value, null);
}
While expressions are used by moq to setup mocks, you are trying to mock an expression. This tends to be very difficult with Moq but there are workarounds via matched arguments.
Assuming Scope.Execute method is defined like
public interface IScope {
Result<TResult> Execute<T, TResult>(string temp, Func<T, TResult> function);
}
Use It.IsAny to allow for flexibility when setting up a mock that relies on an expression argument.
_mockedScope
.Setup(x => x.Execute<FooService, IList<Foo>>(It.IsAny<string>(), It.IsAny<Func<FooService, IList<Foo>>>()))
.Returns(Result<IList<Foo>>.Success(new List<Foo>()));
The It.IsAny<Func<FooService, IList<Foo>>>() will cover s => s.GetFoo(foo) in the invoked code.
Given
public class Test {
private readonly IScope _scope;
public Test(IScope scope) {
_scope = scope;
}
public IList<Foo> Foo() {
var foo = new FooEntity();
Result<IList<Foo>> result = _scope.Execute<FooService, IList<Foo>>("f", s => s.GetFoo(foo));
var value = result.Value;
return value;
}
}
The following complete example was used to demonstrate what was explained above
[TestClass]
public class ExpressionMock {
[TestMethod]
public void TestFoo() {
//Arrange
var _mockedScope = new Mock<IScope>();
_mockedScope
.Setup(x => x.Execute<FooService, IList<Foo>>(It.IsAny<string>(), It.IsAny<Func<FooService, IList<Foo>>>()))
.Returns(Result<IList<Foo>>.Success(new List<Foo>()));
var subject = new Test(_mockedScope.Object);
//Act
var actual = subject.Foo();
//Assert
actual.Should().NotBeNull();
}
}
Reference Moq Quickstart to get a better understanding of how to use the framework
I'm pretty new to xUnit and here's what I'd like to achieve:
[Theory]
[InlineData((Config y) => y.Param1)]
[InlineData((Config y) => y.Param2)]
public void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
Basically, I have a GenericConfig object which contains Config and other kind of configurations, but I need to check that every single parameter is valid. Since they're all string, I wanted to simplify using [InlineData] attribute instead of writing N equals tests.
Unfortunately the error I'm getting is "Cannot convert lambda expression to type 'object[]' because it's not a delegate type", which is pretty much clear.
Do you have any idea on how to overcome this?
In addition to the already posted answers. The test cases can be simplified by directly yielding the lambdas.
public class ConfigTestDataProvider
{
public static IEnumerable<object[]> TestCases
{
get
{
yield return new object [] { (Func<Config, object>)((x) => x.Param1) };
yield return new object [] { (Func<Config, object>)((x) => x.Param2) };
}
}
}
This test ConfigTestDataProvider can then directly inject the lambdas.
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(Func<Config, object> func)
{
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
Actually, I was able to find a solution which is a bit better than the one provided by Iqon (thank you!).
Apparently, the InlineData attribute only supports primitive data types. If you need more complex types, you can use the MemberData attribute to feed the unit test with data from a custom data provider.
Here's how I solved the problem:
public class ConfigTestCase
{
public static readonly IReadOnlyDictionary<string, Func<Config, string>> testCases = new Dictionary<string, Func<Config, string>>
{
{ nameof(Config.Param1), (Config x) => x.Param1 },
{ nameof(Config.Param2), (Config x) => x.Param2 }
}
.ToImmutableDictionary();
public static IEnumerable<object[]> TestCases
{
get
{
var items = new List<object[]>();
foreach (var item in testCases)
items.Add(new object[] { item.Key });
return items;
}
}
}
And here's the test method:
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(string currentField)
{
var func = ConfigTestCase.testCases.FirstOrDefault(x => x.Key == currentField).Value;
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
I could maybe come up with something a bit better or cleaner, but for now it works and the code is not duplicated.
I have the problem the same to you, and I found the solution that using TheoryData class and MemberData attribute. Here is the example and I hope the code usefully:
public class FooServiceTest
{
private IFooService _fooService;
private Mock<IFooRepository> _fooRepository;
//dummy data expression
//first parameter is expression
//second parameter is expected
public static TheoryData<Expression<Func<Foo, bool>>, object> dataExpression = new TheoryData<Expression<Func<Foo, bool>>, object>()
{
{ (p) => p.FooName == "Helios", "Helios" },
{ (p) => p.FooDescription == "Helios" && p.FooId == 1, "Helios" },
{ (p) => p.FooId == 2, "Poseidon" },
};
//dummy data source
public static List<Foo> DataTest = new List<Foo>
{
new Foo() { FooId = 1, FooName = "Helios", FooDescription = "Helios Description" },
new Foo() { FooId = 2, FooName = "Poseidon", FooDescription = "Poseidon Description" },
};
//constructor
public FooServiceTest()
{
this._fooRepository = new Mock<IFooRepository>();
this._fooService = new FooService(this._fooRepository.Object);
}
[Theory]
[MemberData(nameof(dataExpression))]
public void Find_Test(Expression<Func<Foo, bool>> expression, object expected)
{
this._fooRepository.Setup(setup => setup.FindAsync(It.IsAny<Expression<Func<Foo, bool>>>()))
.ReturnsAsync(DataTest.Where(expression.Compile()));
var actual = this._fooService.FindAsync(expression).Result;
Assert.Equal(expected, actual.FooName);
}
}
Oddly delegates are not objects, but Actions or Funcs are. To do this, you have to cast the lambda to one of these types.
object o = (Func<Config, string>)((Config y) => y.Param1)
But doing this, your expression is not constant anymore. So this will prevent usage in an Attribute.
There is no way of passing lambdas as attributes.
One possible solution would be to use function calls, instead of attributes. Not as pretty, but could solve your problem without duplicate code:
private void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
[Theory]
public Test1()
{
HasConfiguration((Config y) => y.Param1);
}
[Theory]
public Test2()
{
HasConfiguration((Config y) => y.Param2);
}
public class HrcpDbTests
{
[Theory]
[MemberData(nameof(TestData))]
public void Test(Expression<Func<bool>> exp)
{
// Arrange
// Act
// Assert
}
public static IEnumerable<object[]> TestData
{
get
{
Expression<Func<bool>> mockExp1 = () => 1 == 0;
Expression<Func<bool>> mockExp2 = () => 1 != 2;
return new List<object[]>
{
new object[]
{
mockExp1
},
new object[]
{
mockExp2
}
}
}
}
}
I have an NUnit unit test which I have two collections of different types which I want to assert are equivalent.
class A { public int x; }
class B { public string y; }
[Test]
public void MyUnitTest()
{
var a = GetABunchOfAs(); // returns IEnumerable<A>
var b = GetABunchOfBs(); // returns IEnumerable<B>
Assert.IsPrettySimilar(a, b, (argA, argB) => argA.ToString() == argB);
}
where Assert.IsPrettySimilar is defined like such
public static void IsPrettySimilar<T1, T2>(
IEnumerable<T1> left,
IEnumerable<T2> right,
Func<T1, T2, bool> predicate)
{
using (var leftEnumerator = left.GetEnumerator())
using (var rightEnumerator = right.GetEnumerator())
{
while (true)
{
var leftMoved = leftEnumerator.MoveNext();
if (leftMoved != rightEnumerator.MoveNext())
{
Assert.Fail("Enumerators not of equal size");
}
if (!leftMoved)
{
break;
}
var isMatch = predicate(leftEnumerator.Current,
rightEnumerator.Current);
Assert.IsTrue(isMatch);
}
}
}
My question is, is there a more idiomatic way of doing the above with the existing methods in NUnit? I already looked at CollectionAssert and there's nothing matching what I want to do.
My description of "equivalent" in this case is:
1) Collections must be of same size
2) Collections must be in same logical order
3) Some predicate must be used to determine equivalence between matching items.
Let's think what you are trying to test. You are not trying to test that objects from first sequence are same as objects from second sequence. They can be very different. So, word similar is very vague here. What you really trying to test here, is that some projection of first sequence is equal to other projection of second sequence. And NUnit already have such functionality:
CollectionAssert.AreEqual(bunchOfAs.Select(a => a.ToString()),
bunchOfBs.Select(b => b));
Thus you are projecting both sequences to get particular data, then you can give nice names for these two projections, which will make your test readable to others. You have some hidden business logic here, which does not have explanation in code - you don't explain why you making such projections. So, nice names of projection results will explain your intent. E.g:
var expectedNames = employees.Select(u => u.Login);
var actualNames = customers.Select(c => c.Name);
CollectionAssert.AreEqual(expectedNames, actualNames);
That is much cleaner for me than
Assert.IsPrettySimilar(employees, customers, (e, c) => u.Login == c.Name);
I know you looked into CollectionAssert, however, I have found a strategy like this very useful in my own tests.
Overriding ToString and Equals on the objects makes this test pass.
[TestFixture]
public class Class1
{
[Test]
public void MyUnitTest()
{
var a = GetABunchOfAs(); // returns IEnumerable<A>
var b = GetABunchOfBs(); // returns IEnumerable<B>
CollectionAssert.AreEqual(a, b.OrderBy(x => x.y));
}
public List<A> GetABunchOfAs()
{
return new List<A>
{
new A() {x = 1},
new A() {x = 2},
new A() {x = 3},
new A() {x = 4}
};
}
public List<B> GetABunchOfBs()
{
return new List<B>
{
new B() {y = "4"},
new B() {y = "1"},
new B() {y = "2"},
new B() {y = "3"},
};
}
}
public class A
{
public int x;
public override bool Equals(object obj)
{
return obj.ToString().Equals(x.ToString());
}
public override string ToString()
{
return x.ToString();
}
}
public class B
{
public string y;
public override string ToString()
{
return y;
}
public override bool Equals(object obj)
{
return obj.ToString().Equals(y);
}
}
I deliberately left GetABunchOfBs out of order, however the test still passes.
It looks like Sergey's answer is the one I'm looking for (which was to see whether NUnit already has a facility to do what I want). However, this is the solution I ended up with, which is closer to the implementation I want.
public static class EnumerableAssert
{
public static void AreEquivilent<TExpected, TActual>(IEnumerable<TExpected> expected, IEnumerable<TActual> actual, Func<TExpected, TActual, bool> predicate)
{
if (ReferenceEquals(expected, actual))
{
return;
}
using (var expectedEnumerator = expected.GetEnumerator())
using (var actualEnumerator = actual.GetEnumerator())
{
while (true)
{
var expectedMoved = expectedEnumerator.MoveNext();
if (expectedMoved != actualEnumerator.MoveNext())
{
Assert.Fail("Expected and Actual collections are of different size");
}
if (!expectedMoved)
{
return;
}
Assert.IsTrue(predicate(expectedEnumerator.Current, actualEnumerator.Current));
}
}
}
}