Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a7a8ac2
Added property IsXmlIgnored to IMember.cs. Implemented in Field.cs, P…
logiclrd Oct 1, 2025
16376cd
Added property IsNonSerialized to IMember.cs. Implemented it in Field…
logiclrd Oct 1, 2025
31c7eeb
Added property IsIgnoredDataMember to IMember.cs. Implemented it in F…
logiclrd Oct 1, 2025
9825cc1
Updated documentation and releases.md to describe changes.
logiclrd Oct 1, 2025
115616f
Updated the verified API surface.
logiclrd Oct 1, 2025
b49a133
Non-semantic code style tweak in Field.cs, per Qodana analysis.
logiclrd Oct 2, 2025
e9da993
Removed methods ExcludingXmlIgnoredMembers, IncludingXmlIgnoredMember…
logiclrd Oct 9, 2025
3a30618
Made ExcludeXmlIgnoredMembersRule, ExcludeIgnoredDataMembersRule and …
logiclrd Oct 9, 2025
34d355b
Reran AcceptApiChanges.ps1.
logiclrd Oct 9, 2025
63bec95
Deleted properties IsXmlIgnored, IsIgnoredDataMember and IsNonSeriali…
logiclrd Oct 11, 2025
ba5cc41
Fixed incomplete documentation.
logiclrd Oct 11, 2025
256d23a
Made examples in the documentation more relevant.
logiclrd Oct 11, 2025
cbde71e
Removed using directives that are no longer used from Field.cs, Objec…
logiclrd Oct 12, 2025
b3bf5c7
Merge remote-tracking branch 'upstream/main' into xmlignore
logiclrd Oct 12, 2025
636708d
Moved the Ignored Members section from _pages/tips.md to new page _pa…
logiclrd Oct 13, 2025
968a389
Updated the link in basicassertions.md to track the relocation of inf…
logiclrd Oct 13, 2025
85a9f1e
Removed references to classes that no longer exist from the names of …
logiclrd Oct 13, 2025
65b2472
ObjectAssertionSpecs.BeXmlSerializable/BeDataContractSerializable: Us…
logiclrd Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions Src/FluentAssertions/ObjectAssertionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ public static AndConstraint<ObjectAssertions> BeDataContractSerializable(this Ob
return BeDataContractSerializable<object>(assertions, options => options, because, becauseArgs);
}

/// <summary>
/// Asserts that an object can be serialized and deserialized using the data contract serializer and that it stills retains
/// the values of all members.
/// </summary>
/// <param name="options">
/// A reference to the <see cref="EquivalencyOptions{Object}"/> configuration object that can be used
/// to influence the way the object graphs are compared. You can also provide an alternative instance of the
/// <see cref="EquivalencyOptions{Object}"/> class. The global defaults are determined by the
/// <see cref="AssertionConfiguration"/> class.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception>
public static AndConstraint<ObjectAssertions> BeDataContractSerializable(this ObjectAssertions assertions,
Comment thread
dennisdoomen marked this conversation as resolved.
Func<EquivalencyOptions<object>, EquivalencyOptions<object>> options,
[StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
return BeDataContractSerializable<object>(assertions, options, because, becauseArgs);
}

/// <summary>
/// Asserts that an object can be serialized and deserialized using the data contract serializer and that it stills retains
/// the values of all members.
Expand Down Expand Up @@ -96,13 +121,59 @@ private static object CreateCloneUsingDataContractSerializer(object subject)
/// </param>
public static AndConstraint<ObjectAssertions> BeXmlSerializable(this ObjectAssertions assertions,
[StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
=> BeXmlSerializable<object>(assertions, options => options, because, becauseArgs);

/// <summary>
/// Asserts that an object can be serialized and deserialized using the XML serializer and that it stills retains
/// the values of all members.
/// </summary>
/// <param name="options">
/// A reference to the <see cref="EquivalencyOptions{Object}"/> configuration object that can be used
/// to influence the way the object graphs are compared. You can also provide an alternative instance of the
/// <see cref="EquivalencyOptions{Object}"/> class. The global defaults are determined by the
/// <see cref="AssertionConfiguration"/> class.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public static AndConstraint<ObjectAssertions> BeXmlSerializable(this ObjectAssertions assertions,
Comment thread
dennisdoomen marked this conversation as resolved.
Func<EquivalencyOptions<object>, EquivalencyOptions<object>> options,
[StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
=> BeXmlSerializable<object>(assertions, options, because, becauseArgs);

/// <summary>
/// Asserts that an object can be serialized and deserialized using the XML serializer and that it stills retains
/// the values of all members.
/// </summary>
/// <param name="options">
/// A reference to the <see cref="EquivalencyOptions{T}"/> configuration object that can be used
/// to influence the way the object graphs are compared. You can also provide an alternative instance of the
/// <see cref="EquivalencyOptions{T}"/> class. The global defaults are determined by the
/// <see cref="AssertionConfiguration"/> class.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public static AndConstraint<ObjectAssertions> BeXmlSerializable<T>(this ObjectAssertions assertions,
Func<EquivalencyOptions<T>, EquivalencyOptions<T>> options,
[StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
try
{
object deserializedObject = CreateCloneUsingXmlSerializer(assertions.Subject);
var deserializedObject = CreateCloneUsingXmlSerializer(assertions.Subject);

EquivalencyOptions<T> defaultOptions = AssertionConfiguration.Current.Equivalency.CloneDefaults<T>()
.PreferringRuntimeMemberTypes().IncludingFields().IncludingProperties();

deserializedObject.Should().BeEquivalentTo(assertions.Subject,
options => options.PreferringRuntimeMemberTypes().IncludingFields().IncludingProperties());
deserializedObject.Should().BeEquivalentTo((T)assertions.Subject, _ => options(defaultOptions));
}
catch (Exception exc)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,11 @@ namespace FluentAssertions
public static class ObjectAssertionsExtensions
{
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
}
public abstract class OccurrenceConstraint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,11 @@ namespace FluentAssertions
public static class ObjectAssertionsExtensions
{
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
}
public abstract class OccurrenceConstraint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,11 @@ namespace FluentAssertions
public static class ObjectAssertionsExtensions
{
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
}
public abstract class OccurrenceConstraint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,11 @@ namespace FluentAssertions
public static class ObjectAssertionsExtensions
{
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeDataContractSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<object>, FluentAssertions.Equivalency.EquivalencyOptions<object>> options, string because = "", params object[] becauseArgs) { }
public static FluentAssertions.AndConstraint<FluentAssertions.Primitives.ObjectAssertions> BeXmlSerializable<T>(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func<FluentAssertions.Equivalency.EquivalencyOptions<T>, FluentAssertions.Equivalency.EquivalencyOptions<T>> options, string because = "", params object[] becauseArgs) { }
}
public abstract class OccurrenceConstraint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,37 @@ public void When_an_object_is_data_contract_serializable_but_doesnt_restore_all_
}

[Fact]
public void When_a_data_contract_serializable_object_doesnt_restore_an_ignored_property_it_should_succeed()
public void When_an_object_is_not_binary_serializable_and_has_ignored_properties_it_should_fail()
{
// Arrange
var subject = new DataContractSerializableClassNotRestoringAllProperties
var subject = new DataContractSerializableClassWithIgnoredDataMember()
{
Name = "John",
BirthDay = 20.September(1973)
Name = "Deborah",
CachedSum = 602_214_076_000_000_000_000_000M,
};

// Act
Action act = () => subject.Should()
.BeDataContractSerializable<DataContractSerializableClassNotRestoringAllProperties>(
options => options.Excluding(x => x.Name));
Action act = () => subject.Should().BeDataContractSerializable();

// Assert
act.Should().NotThrow();
act.Should().Throw<XunitException>().WithMessage("*Expected property subject.CachedSum to be*");
}

[Fact]
public void When_an_object_is_binary_serializable_and_has_nonserialized_fields_it_should_fail()
{
// Arrange
var subject = new BinarySerializableClassWithNonSerializedMember()
{
Name = "Deborah",
CachedSum = 602_214_076_000_000_000_000_000M,
};

// Act
Action act = () => subject.Should().BeDataContractSerializable(options => options);

// Assert
act.Should().Throw<XunitException>().WithMessage("*Expected field subject.CachedSum to be*");
}

[Fact]
Expand Down Expand Up @@ -119,6 +134,26 @@ public class DataContractSerializableClassNotRestoringAllProperties
public DateTime BirthDay { get; set; }
}

[DataContract]
public class DataContractSerializableClassWithIgnoredDataMember
{
[DataMember]
public string Name { get; set; }

[IgnoreDataMember]
public decimal CachedSum { get; set; }
}

[Serializable]
public class BinarySerializableClassWithNonSerializedMember
{
// These members need to be fields for binary serialization.
public string Name;

[NonSerialized]
public decimal CachedSum;
}

public enum Color
{
Red = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ public void When_an_object_is_xml_serializable_but_doesnt_restore_all_properties
act.Should().Throw<XunitException>()
.WithMessage("*to be serializable, but serialization failed with:*Name*to be*");
}

[Fact]
public void When_an_object_is_xml_serializable_and_has_ignored_properties_it_should_fail()
{
// Arrange
var subject = new XmlSerializableClassWithIgnoredProperties()
{
Name = "Deborah",
CachedSum = 602_214_076_000_000_000_000_000M,
};

// Act
Action act = () => subject.Should().BeXmlSerializable();

// Assert
act.Should().Throw<XunitException>().WithMessage("*Expected property subject.CachedSum to be*");
}
}

public class XmlSerializableClass
Expand Down Expand Up @@ -99,6 +116,34 @@ public void WriteXml(XmlWriter writer)
}
}

public class XmlSerializableClassWithIgnoredProperties
{
public string Name { get; set; }

[XmlIgnore]
public decimal CachedSum { get; set; }

public R Splonk { get; set; } =
new R()
{
A = new Q() { Flarbs = "eh" },
B = new Q() { Flarbs = "bee" },
};
}

public class R
{
public Q A { get; set; }

public Q B { get; set; }
}

public class Q
{
[XmlIgnore]
public string Flarbs { get; set; }
}

internal class NonPublicClass
{
[UsedImplicitly]
Expand Down
2 changes: 2 additions & 0 deletions docs/_data/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ sidebar:
url: /tips/#improved-assertions
- title: Migrating from MSTest
url: /tips/#mstest-migration
- title: Serialization & Ignored Members
url: /serializationignoredmembers

- title: Upgrading
url: /upgradingtov6
Expand Down
2 changes: 2 additions & 0 deletions docs/_pages/basicassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ We’ve also added the possibility to assert that an object can be serialized an
theObject.Should().BeXmlSerializable();
theObject.Should().BeDataContractSerializable();
```

Comment thread
dennisdoomen marked this conversation as resolved.
See [Tips & Tricks - Serialization & Ignored Members](serializationignoredmembers.md) for additional information about the `BeXmlSerializable` and `BeDataContractSerializable` assertions and ignored members.
6 changes: 6 additions & 0 deletions docs/_pages/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ sidebar:
nav: "sidebar"
---

## Unreleased

### What's new

* New overloads of `BeXmlSerializable` and `BeDataContractSerializable` that allow `EquivalencyOptions` to be configured, allowing the use of member selection rules. - [#3107](https://github.com/fluentassertions/fluentassertions/pull/3107)

## 8.8.0

### What's new
Expand Down
Loading