New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an option to BeEquivalentTo that compares types of objects recursively. #798
Comments
If your AST class overrides But I agree that it could be beneficial to have some option on Just for the note: class Base
{
public string Name { get; set; }
}
class Derived : Base
{
public int Age { get; set; }
}
[Fact]
public void MyTestMethod()
{
Base a = new Base();
Base b = new Derived() { Age = 42 };
a.Should().BeEquivalentTo(b); // Success
a.Should().BeEquivalentTo(b, o => o.RespectingRuntimeTypes()); // Fail
} |
Thank you.
class Base: List<string>
{
public string ClassName => GetType().Name;
}
class OneThing: Base
{
public string Name => "Test";
}
class OtherThing: Base
{
public string Name => "Test";
}
[TestMethod]
public void MyTestMethod()
{
new OneThing().Should().BeEquivalentTo(new OtherThing(), o => o.RespectingRuntimeTypes());
//new OneThing().ClassName.Should().Be(new OtherThing().ClassName);
} |
The framework currently assumes that when a class implements If possible you could change |
Thank you. Introducing ClassName property and extracting collection to a separate property fixed my problem. But generally I think, that adding options for comparing classes and compare both properties and elements would help to reduce false passing tests (they are more difficult to detect then false failing tests and can lead to hiding bugs). Maybe even introduce a new method with different defaults (respect runtime types, compare class names, compare both properties and contents)? |
Is there any option for equivalence to make sure the element types (not their properties, the elements themselves) are not the same? As an example, I have two collections with different element types, although the elements in each collection have the exact same properties and property types _result.Should().BeEquivalentTo(_events, opt =>
opt.RespectingRuntimeTypes()); I would expect that assertion to fail as the element types are different, but it passes. |
What's the type of |
it's a list of different implementations of the same marker interface It's for a sample like the following where there are two collections. One of them stores some objects with certain properties. The other stores some different objects of a different class although this class has the same name and the same properties //...
_events =
new List<IPersistableEvent>
{
new FakeEventOne(),
new FakeEventTwo()
};
_result =
new List<object>{
new AnotherNamespace.FakeEventOne(),
new AnotherNamespace.FakeEventTwo()
}; and basically I was thinking that asserting that both are equivalent respecting runtime types would fail. But it seems it's only taking into consideration property types and not the element types themselves. |
I think this is related? This used to work: result.ShouldBeEquivalentTo(new IFragmentInjector[]
{
new BibliographicIdentifierInjector(),
new AgeClassificationInjector(),
new SalesRightsCountryExpanderInjector(mock.Object)
}); Upgraded to the latest version and this now fails with "No members were found for comparison". |
@grahambunce result.Should().BeEquivalentTo(
new IFragmentInjector[]{...},
opt => opt.RespectingRuntimeTypes()) If that does not help, please open a new issue. |
@jnyrup Ok - tried that and no, it doesn't work. |
The entire existence of the |
@dennisdoomen ok... I guess that makes sense. It’s just the old api passes and it’s been removed so the alternative api fails because the object being compared is an interface that has no types. Perhaps the old api had a bug? Is there a way to provide a custom comparer? I see something but it looks complicated with a lot of things to implement when a func that returns true/false would suffice |
I solved this by using a custom [TestMethod]
public void MyTestMethod()
{
a.Should().BeEquivalentTo(b, options => options
.RespectingRuntimeTypes()
.Using(new TypeEqualityEquivalencyStep()));
}
public class TypeEqualityEquivalencyStep : IEquivalencyStep
{
public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config)
{
return true; // Always handle
}
public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator
structuralEqualityValidator, IEquivalencyAssertionOptions config)
{
var subjectType = context.Subject?.GetType();
var expectationType = context.Expectation?.GetType();
subjectType?.Should().Be(expectationType, context.Because, context.BecauseArgs);
if (subjectType?.GetProperties().Any() != true)
{
return true; // Don't compare empty class
}
return false; // Continue to next step
}
} |
Urs Enzler has an alternative I quite unexpectedly fell into this trap as well - had tests passing which should be failing. I always understood the Therefore I'd very much like FluentAssertions to have an equivalency verification in a more stricter sense. Nitpicking: I don't think having the same property values necessarily constitutes equivalency (in some cases it will). Salary { Dollars = 5000 } vs BossSalary { Dollars 5000, Gold = 5 tons } Pretty sure you wouldn't consider these salaries as equivalent. But might actually consider these two as equivalent: DollarSalary { Dollars = 5000 } vs EuroSalary { Euros = 4250 } (at the time of writing 5000 USD are worth 4250 EUR). As such the difference between "BeEqual" and "BeEquivalentTo" is not immediately obvious. On a conceptual level I'd prefer assertions to fail more often, and then having to opt in to being less strict. That would save me from making wrong assumptions. Let me end by saying thank you for the great FluentAssertions library you've provided and maintained for many years now. |
I'm thinking about providing a PR for this. @dennisdoomen @jnyrup Would you be willing to accept one? What are your design ideas on the problem? I'd actually need it in a way that I can not just turn it on/off but also configure the "strictness" of the type equality per type (also see below). I looked a bit more into this and I've managed to hack this "on top" of FluentAssertions in a way which supports our current requirements but is very hacky.
Guess this is probably also the case for EntityFramework and other ORM's.
Therefore I envision this: BeEquivalentTo(..., _ => _.WithStrictTypeEquality()) // The following works a bit similar to .ComparingByMembers() // And maybe, for full customization: |
Sure. For those people that need this kind of strictness, it might make total sense. I think it all comes down to add the corresponding options on the options object and then build a custom |
I've looked into this a bit closer. I see the following obstacles to adding configurability for exact type matching:
@dennisdoomen Is my assumption correct that this feature is not important enough to warrant a redesign of the Equivalency comparison config API and implementation? |
I've updated my However, this would be a needed feature in FluentAssertions, perhaps with a separate opt-in (global) setting. Earlier, I always assumed types were compared, until I discovered that many tests were not failing even when they should have been... |
I personally don't see enough value in it to redesign the equivalency API for it. But if somebody can provide the necessary PRs to make it possible as an option, I would welcome it and willing to support it thereafter. |
Hi @dennisdoomen I'm glad to find this thread and see our team wasn't the only one running into this. Also glad to see a willingness to add it and support it. We made PR #1704 for this. I'm sure there are plenty of things to correct but we copied the code we have been using for around 3 years now and just refactored to work with version 6. |
Yeah, I'd like to see this. Although the current |
I think a good way to do this could be using an internal identifier that allows loose comparisons. For example: "My string".Should().BeEquivalentTo(It.Should().BeString()); Or even: "My string".Should().BeEquivalentTo(It.Should().Match(".*string.*")); It looks ambiguous to other options the package already offer, but the real power of this options appears when you deal with subproperties: (new {
SubProp1 = 123,
SubProp2 = true,
SubProp3 = DateTime.Now.AddDays(10),
SubProp4 = "test123"
}).Should().BeEquivalentTo(new {
SubProp1 = It.Should().BeInteger(),
SubProp2 = It.Should().BeBoolean(),
SubProp3 = It.Should().BeDateTime().CloseTo(DateTime.Now.AddDays(10)),
SubProp4 = It.Should().Match("test.*")
}); This way, you allow a clear and easy to understand way to customize recursive assertions |
What would that |
I am trying to compare AST and found that
BeEquivalentTo
has no option to instruct the library to check types on comparing. For example, I expect the way to modify the following test code to fail because of class names do not match. This behavior should be recursive (so if property value does not have exactly the same type, comparison should fail). I see that stackoverflow has a question on this.The text was updated successfully, but these errors were encountered: