Replies: 9 comments
-
From what I can see, you're looking for a very specific use case that will most likely be very niche. The closest I can think of is the |
Beta Was this translation helpful? Give feedback.
-
I find it interesting you feel that this is niche. You obviously have observed a wider market / set of use cases than I have. I personally feel like I run into this a lot. Maybe it has to do with me not really approaching my unit tests correctly. For example, I want to verify that a particular method I call logs a warning. There's other logs in there I don't care about, like debug and informational logs. But I need to make sure one or two warnings appear in that list. Another example, I have a method that parses a markdown document. For each header in the document, it parses out the header and certain keywords out of any paragraphs under it. In my test, I only want to check that two or more specific headers got parsed and ignore the rest. Furthermore, the dataset returned is a list of some complex ref type. The test should only pass if there exists 2 objects in the list that match a specific subset, and that subset may only look at specific properties on each object (not the full object graph). If you were to tell me these are pretty sophisticated tests, I'd agree with you. But niche is something I can't really comment on. After all I only see what is right in front of me. I'm hoping you can tell me I'm doing something wrong here. Because if not, it will continue to be a bit of a struggle to find a readable and convenient means to perform these types of customized subset assertions. Thanks for your quick response. |
Beta Was this translation helpful? Give feedback.
-
For those, you can use the overload of
I guess you could use two calls of
You're not doing anything wrong here. There are so many different ways of testing. I'm already happy that you are writing unit tests in the first place. And to be fair, I regularly run into scenarios where I wonder if there could not be a simpler way to assert some complicated scenario. But often, either I can come up with a syntax that would be incredibly hard to explain to others, or I simply can't find anything that the compiler would be able to handle. |
Beta Was this translation helpful? Give feedback.
-
Here's an example of using private class LogEvent
{
public LogEvent(string text) => MessageTemplate = new() { Text = text };
public MessageTemplate MessageTemplate { get; set; }
public int IgnoreMe { get; set; }
}
private class MessageTemplate
{
public string Text { get; set; }
public int AlsoIgnoreMe { get; set; }
}
[Fact]
public void ComplexObjects()
{
var subject = new LogEvent[]
{
new("...Foo..."),
new("...bAr..."),
new("...baZ...")
};
var expected = new[]
{
new { MessageTemplate = new { Text = "foo" } },
new { MessageTemplate = new { Text = "bar" } },
};
foreach (var item in expected)
{
subject.Should().ContainEquivalentOf(item, opt => opt
.Using<string>(ctx => ctx.Subject.Should().ContainEquivalentOf(ctx.Expectation))
.WhenTypeIs<string>());
}
} If you want a more readable test body (which is often a desirable goal for unit tests), you could make an extension method [Fact]
public void ComplexObjectsCustom()
{
var subject = new LogEvent[]
{
new("...Foo..."),
new("...bAr..."),
new("...baZ...")
};
var expected = new[]
{
new { MessageTemplate = new { Text = "foo" } },
new { MessageTemplate = new { Text = "bar2" } },
};
subject.Should().ContainEquivalentsOf(expected, opt => opt
.Using<string>(ctx => ctx.Subject.Should().ContainEquivalentOf(ctx.Expectation))
.WhenTypeIs<string>());
} internal static class GenericCollectionAssertionExtensions
{
[CustomAssertion]
public static AndConstraint<TAssertions> ContainEquivalentsOf<TSubject, TAssertions, TExpectation>(
this CollectionAssertions<TSubject, TAssertions> parent,
IEnumerable<TExpectation> expectations,
Func<EquivalencyAssertionOptions<TExpectation>, EquivalencyAssertionOptions<TExpectation>> config,
string because = "",
params object[] becauseArgs)
where TSubject : IEnumerable
where TAssertions : CollectionAssertions<TSubject, TAssertions>
{
using var scope = new AssertionScope();
foreach (TExpectation expectation in expectations)
{
parent.ContainEquivalentOf(expectation, config);
}
string[] failures = scope.Discard();
Execute.Assertion
.ForCondition(!failures.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:collection} {0} to contain equivalents of {1}{reason}.", parent.Subject, expectations);
return new((TAssertions)parent);
}
} When FluentAssertions 6.0 is released, it can be improved a bit to also return the matched equivalents. internal static class GenericCollectionAssertionExtensions
{
[CustomAssertion]
public static AndWhichConstraint<TAssertions, IEnumerable<T>> ContainEquivalentsOf<TCollection, T, TAssertions, TExpectation>(
this GenericCollectionAssertions<TCollection, T, TAssertions> parent,
IEnumerable<TExpectation> expectations,
Func<EquivalencyAssertionOptions<TExpectation>, EquivalencyAssertionOptions<TExpectation>> config,
string because = "",
params object[] becauseArgs)
where TCollection : IEnumerable<T>
where TAssertions : GenericCollectionAssertions<TCollection, T, TAssertions>
{
using var scope = new AssertionScope();
var equivalents = new List<T>();
foreach (TExpectation expectation in expectations)
{
T equivalent = parent.ContainEquivalentOf(expectation, config).Which;
equivalents.Add(equivalent);
}
string[] failures = scope.Discard();
Execute.Assertion
.ForCondition(!failures.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:collection} {0} to contain equivalents of {1}{reason}.", parent.Subject, expectations);
return new((TAssertions)parent, equivalents);
}
} Once |
Beta Was this translation helpful? Give feedback.
-
Sorry I've unintentionally over-simplified my example. But what I need is to be able to specify the expected container and a lambda. Right now that option doesn't seem to exist: The current version that takes a lambda will stop after the first match. I need it to continue evaluating the lambda until the number of I remember looking at
Yeah that's essentially what I'm doing now, except with a
Thanks, I needed this reassurance. It's definitely worth mentioning (and I should have made this clear at the start if I forgot to) that there's no real issue here. Fluent Assertions is an amazing library that already makes things flow in a more human readable way. At the scale of problems it is solving, this is really just a nitpick. Part of this was me feeling like I wasn't fitting into the fluent model very well. But you're right there's a lot of variations. A single foreach, at the end of the day, isn't a big deal. I very much value and appreciate your feedback. Thank you very much. |
Beta Was this translation helpful? Give feedback.
-
Thank you for taking the time to suggest improvements. |
Beta Was this translation helpful? Give feedback.
-
@jnyrup I love the examples, thank you. Your idea certainly offers some inspiration especially for the more complicated scenarios I talked about. I do wonder then if I'm happy to close this issue as I feel like reasonably close solutions to what I wanted have been explained here. Only reason I'm not closing it right away is because it might serve as a ticket to track the idea @jnyrup shared. Either way, if you'd like to close this I am perfectly fine with that. Thanks again to you both. |
Beta Was this translation helpful? Give feedback.
-
:) I spent an hour or two trying to find an My use case is an integration test not a unit test so maybe that's why I can see needing I am writing an REST API wrapper to communicate with a 3rd party. We are using Refit, Polly and Newtonsoft.json. Unfortunately the 3rd party's documentation needs some work and the endpoints return many undocumented properties. Also they change things often without notification. I am writing tests to make sure that the return type classes we have created match the Json they send back. I plan to run the tests often to make sure we get notified if/when something breaks. So to test, I make a call with our library and then a "raw call" that returns a Newtonsoft.Json JToken. I then map the props of "our" object to a ours.Should().BeEquivalentSubsetOf(JTokenPropList) and have it act like Any suggestions different than what's listed above? |
Beta Was this translation helpful? Give feedback.
-
@meeple142 maybe this is what you need? It's using a custom extension method that tries to deserialize the JSON into an anonymous type defined by an existing anonymous type and then uses FA to compare that anonymous type with itself. |
Beta Was this translation helpful? Give feedback.
-
Description
I have a container (superset) with 3 strings in it. I need to make sure that 2 strings are present in that container. Usually this comparison requires doing either a substring search or a case insensitive comparison. In either case, this involves specifying a lambda predicate for the test in the assertion.
BeSubsetOf()
exists, but this requires the equals method be implemented on the objects. It would be ideal to have a similar method for testing for equivalency, especially when the subset check involves complex objects. Something likeBeEquivalentSubsetOf()
?Complete minimal example reproducing the issue
Below is an example of how I execute my test today.
Expected behavior:
What would be ideal is to do something like this:
Using my earlier example:
Versions
Using version
5.*
with .NET 5Beta Was this translation helpful? Give feedback.
All reactions