Closed ghost closed 5 years ago
To be honest, I think it's a scary way to compare two types. Just imagine what happens if Username
suddenly become null
. But if your intention is to compare only certain properties, you can pass in an anonymous type instead of user
that only defines the properties that you care about.
user object is building from my BDD table so I know what fields I want to assert with userDTO object
Then The user should have the following values
| Key | Value |
| id | 2 |
| name | Ervin Howell |
| username | Antonette |
| phone | 010-692-6593 x09125 |
| website | anastasia.net |
"you can pass in an anonymous type instead of user that only defines the properties that you care about." what do you mean?
userDTO.Should().BeEquivalentTo(new
{
Key = "Value",
id = 2,
name = "Ervin Howell",
username = "Antonette",
phone = "010-692-6593 x09125",
website = "anastasia.net"
});
It is not the way I want to use it.
It seems to me we're having a XY-problem (I think the quote is clear, despite it being slightly pejorative)
- User wants to do X.
- User doesn't know how to do X, but thinks they can fumble their way to a solution if they can just manage to do Y.
- User doesn't know how to do Y either.
- User asks for help with Y.
- Others try to help user with Y, but are confused because Y seems like a strange problem to want to solve.
- After much interaction and wasted time, it finally becomes clear that the user really wants help with X, and that Y wasn't even a suitable solution for X.
We're advising against designing tests to exclude member based on their runtime values. That's a recipe for fragile tests. E.g. the test breaks if:
user
get's a new non-null member,user
is null
, then that member would suddenly be excluded from the comparisonWithout knowing the entire context of your setup, I think Dennis' example above is the best way to write the test as it is entirely clear what userDTO
is expected to be.
user object is building from my BDD table so I know what fields I want to assert with userDTO object Then how about this one. It's a bit longer, but it:
- re-uses the values from
CreateInstance
instead of duplicating it- Specifies exactly what members to compares
userDTO
against
var user = table.CreateInstance<User>();
var expected = new
{
Key = user.Key,
id = user.id,
name = user.name,
username = user.username,
phone = user.phone,
website = user.website
}
userDTO.should().BeEquivalentTo(expected);
ExcludingMissingMembers() is not working as expected. Should not compare values if field in expected object is null or missing.
I dont want hardcode new object with null fields in my method. I have user object as expected with possibility with null fields and userDTO object. I want compare non null fields from user object with the same fields in userDTO object
That is all, without creating new object.
var user = table.CreateInstance<User>();
is for create expected object from BDD table, nothing wrong here
@Haxy89 The existing behavior of FluentAssertions is correct for me. The member is not missing if it is null
.
I think your question will result into a new feature like ExcludingNullMembers
.
@Ig2de Yes! This is what i need. I thought excludingmissingmember is in the scope of null member :)
No, when defining an expectation, a missing member is a property or field that the expectation does not have. Normally I would expect my test to fail if the value of a property on the expectation is null
and the subject's property is not.
The only option you have is to implement a custom IMemberMatchingRule
and add it using the Using
method exposed through the options
parameter.
Here's an example on how to recursively ignore members from the expectation
, when their runtime value is null
.
class User
{
public string Name { get; set; }
public string Address { get; set; }
}
class UserDTO
{
public string Name { get; set; }
public string Address { get; set; }
}
class IgnoreNullMembersInExpectation : IEquivalencyStep
{
public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config) => context.Expectation is null;
public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config) => true;
}
[TestClass]
public class IgnoreNullMembers
{
[TestMethod]
public void User_and_UserDTO_only_differs_by_null_property()
{
// Arrange
var userDTO = new UserDTO
{
Name = "foo",
Address = "bar"
};
var user = new User
{
Name = "foo",
Address = null
};
// Assert
userDTO.Should().BeEquivalentTo(user, opt => opt
.Using(new IgnoreNullMembersInExpectation()));
}
[TestMethod]
public void User_and_UserDTO_differs_by_non_null_property()
{
// Arrange
var userDTO = new UserDTO
{
Name = "foo",
Address = "bar"
};
var user = new User
{
Name = "baz",
Address = null
};
// Assert
userDTO.Should().NotBeEquivalentTo(user, opt => opt
.Using(new IgnoreNullMembersInExpectation()));
}
}
@Haxy89 Does the example above works for you?
Closing this issue.
I've given an example implementation of an IEquivalencyStep
.
I don't foresee we would want include that implementation in Fluent Assertions.
Thanks for the IEquivalencyStep example - works like a charm, just what I needed.
userDTO.Should().BeEquivalentTo(new { Key = "Value", id = 2, name = "Ervin Howell", username = "Antonette", phone = "010-692-6593 x09125", website = "anastasia.net" });
The problem I got with this approach is with refactoring. Normally, in VisualStudio, we are able to do global renames but, with the anonymous object approach, it won't be capable of knowing that the property named username in there is the same as the one in the actual class and it won't be renamed ending in a nightmare of semi-manual replaces.
Would it be possible to have some way of using Expressions maybe?
userDTO.Should().BeEquivalentTo(usertDTOType x =>new Dictionary<Expression, object>(){
{() => x.id, 2},
{() => x.name, "Ervin Howell"},
[...]
}
I'm really not sure about the syntax as I'm always mixed up with Expressions but I hope the intent is clear. Am I the only one having this concern? If so, how do you manage? If not, maybe I should file a feature request...
The problem I got with this approach is with refactoring. Normally, in VisualStudio, we are able to do global renames but, with the anonymous object approach, it won't be capable of knowing that the property named username in there is the same as the one in the actual class and it won't be renamed ending in a nightmare of semi-manual replaces.
I assume that if you're using an anonymous type, you're verifying a contract. Renaming the property of the userDto
as part of a refactoring sounds like a breaking change to me and should fail your tests.
In general, you are totally right.
Sorry, I was thinking of my own specific case where I don't expose any public client API. I have an end user app and everything public is part of the same solution so refactoring is not, per se, a breaking change since the whole app is globally built each time hence my "problem".
Hi,
I have code like this:
user:
example userDTO:
{ "email": "testuser01@kl;kl;.com", "password": "nn91PKBrv99yQc/uNqTM2", "id": 2, "name": "Ervin Howell", "username": "Antonette", "address": { "street": "Victor Plains", "suite": "Suite 879", "city": "Wisokyburgh", "zipcode": "90566-7771", "geo": { "lat": "-43.9509", "lng": "-34.4618" } }, "phone": "010-692-6593 x09125", "website": "anastasia.net", "company": { "name": "Deckow-Crist", "catchPhrase": "Proactive didactic contingency", "bs": "synergize scalable supply-chains" } },
I want assert only fields that are not null in user but get exception:
'Expected member Email to be <null>, but found "testuser01@dfhdfh.com". Expected member Password to be <null>, but found "O5jG6//tFKinn91PKBrv99yQc/uNqTM2". Expected member Company to be <null>, but found
Null values still are checked. How to not check null values?