1

I am trying to use FluentAssertions to combine collection and object graph comparison assertions.

I have the following class.

public class Contract
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

Which are returned in a collection, like so.

ICollection<Contract> contracts = factory.BuildContracts();

I then want to make sure that collection contains only specific Contract objects.

contracts.Should().Contain(new Contract() { Id = id1, Name = "A" });

This doesn't work, I'm believe because Contain is using object.Equals rather than object graph comparison, (as provided by ShouldBeEquivalentTo).

I also need to assert that the collection doesn't contain a specific object, i.e.

contracts.Should().NotContain(new Contract() { Id = id2, Name = "B" });

Effectively given a collection containing an unknown number of items, I want to ensure that; it contains a number of specific items, and that it doesn't contain a number of specific items.

Can this be achieved using the functions provided by FluentAssertions?

As a side note, I don't want to override object.Equals for the reasons discussed here. Should I be using IEquatable to ease testing of factories?

Community
  • 1
  • 1
James Wood
  • 17,286
  • 4
  • 46
  • 89

3 Answers3

2

It does use the object.Equals as far as I can tell from documentation and my experience using the framework.

In scenarios like this I tend to use an expression predicate as referenced in the collections documentation for v3.0 and higher.

The following example shows how to make sure that collection contains only specific Contract objects and also assert that the collection doesn't contain a specific object.

[TestMethod]
public void FluentAssertions_Should_Validate_Collections() {
    //Arrange
    var id1 = Guid.NewGuid();
    var id2 = Guid.NewGuid();

    var list = new List<Contract>{
        new Contract() { Id = id1, Name = "A" },
        new Contract() { Id = Guid.NewGuid(), Name = "B"}
    };

    var factoryMock = new Mock<IContractFactory>();
    factoryMock.Setup(m => m.BuildContracts()).Returns(list);
    var factory = factoryMock.Object;

    //Act
    var contracts = factory.BuildContracts();

    //Assert
    contracts.Should()
        .HaveCount(list.Count)
        .And.Contain(c => c.Id == id1 && c.Name == "A")
        .And.NotContain(c => c.Id == id2 && c.Name == "B");
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

You can override your contract Equals which will then be used and your Unit test should pass happily. If you're using random Guids it might be an issue, but it seems like you're using a predefined one.

Give the following a try:

protected bool Equals(Contract other)
{
    return Id.Equals(other.Id) && string.Equals(Name, other.Name);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((Contract) obj);
}

public override int GetHashCode()
{
    unchecked
    {
        return (Id.GetHashCode()*397) ^ (Name != null ? Name.GetHashCode() : 0);
    }
}

and as you can see, it passes the test: enter image description here

Noctis
  • 11,507
  • 3
  • 43
  • 82
  • +1 and thanks for the suggestion. However this isnt really the answer I want, I used to override equals and just use a standard `Assert.IsTrue(contracts.Contains(...))`. But this had its own problems (http://stackoverflow.com/questions/33988487/should-i-be-using-iequatable-to-ease-testing-of-factories). I am hoping to use FluentAssertions to avoid overriding Equals. – James Wood Jun 18 '16 at 10:52
  • 1
    Interesting link ... I'm sure you'll figure something out. I'll keep an eye on the post to see how it evolves :). BTW, have you given "[shouldly](https://github.com/shouldly/shouldly)" a run ? I'm not sure what's its default behavior, but it might work the way you want. – Noctis Jun 18 '16 at 13:19
1

Alternatively to Nkosi's answer, you could still use ShouldBeEquivalentTo by building the expecation.

Community
  • 1
  • 1
Dennis Doomen
  • 8,368
  • 1
  • 32
  • 44
  • Do you mean like `Should().Contain(c => c.ShouldBeEquivalentTo(contract1))`? Not sure how to get that to work as `ShouldBeEquivalentTo` doesn't have a return value. Not quite what you mean by "building the expecation". Thanks – James Wood Jun 18 '16 at 17:07
  • Just create a collection with the `Contract` instances as you expect them and pass that as an expectation to `ShouldBeEquivalentTo`. If you care about a strict order, you can even use the `options => options.WithStrictOrdering`' – Dennis Doomen Jun 19 '16 at 18:06
  • What about if if the collection contains other `Contracts` I don't care about? I.e. the collection contains 100 items, I want to make sure that it contains 2 specific items, and doesn't contain 1 specific item. – James Wood Jun 19 '16 at 19:02