Skip to content
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 several new methods to Types and Methods Assertions #1306

Merged
merged 30 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c7acd2b
Extend TypeSelector and MethodSelector API
fshchudlo Apr 16, 2020
af94d17
Add sealed checks to TypeSelectorAssertions
fshchudlo Apr 16, 2020
9c92646
XML-docs added
fshchudlo Apr 16, 2020
ff7292c
Extend MethodInfoSelectorAssertions
fshchudlo Apr 16, 2020
234a0af
Fix broken link to the documentation
fshchudlo Apr 17, 2020
a1fbaa9
Cleanup
fshchudlo Apr 17, 2020
9c31ec1
Merge branch 'develop' into develop
fshchudlo Apr 21, 2020
31019c5
Fix some of PR comments
fshchudlo Apr 23, 2020
14780bc
Change test names to more descriptive
fshchudlo Apr 23, 2020
a3e8e59
Merge branch 'develop' of https://github.com/fshchudlo/fluentassertio…
fshchudlo Apr 23, 2020
e48d943
Add exception message assertions to the tests
fshchudlo Apr 23, 2020
b122067
Update the docs
fshchudlo Apr 23, 2020
ea1d746
Add missing tests and release notes
fshchudlo Apr 23, 2020
c1c9525
Update Src/FluentAssertions/TypeEnumerableExtensions.cs
fshchudlo Apr 24, 2020
64b1848
Update CONTRIBUTING.md
fshchudlo Apr 24, 2020
9297c5f
PR comments fixes
fshchudlo Apr 24, 2020
e9dff68
Update Src/FluentAssertions/Types/TypeSelector.cs
jnyrup Apr 26, 2020
91f31e8
Update Src/FluentAssertions/Types/TypeSelectorAssertions.cs
jnyrup Apr 26, 2020
a7e2763
Update Tests/FluentAssertions.Specs/Types/TypeSelectorSpecs.cs
jnyrup Apr 26, 2020
11d4955
Update Src/FluentAssertions/Types/TypeSelector.cs
jnyrup Apr 26, 2020
a935006
Update Src/FluentAssertions/Types/TypeSelector.cs
jnyrup Apr 26, 2020
dcb7b43
Update Src/FluentAssertions/TypeEnumerableExtensions.cs
jnyrup Apr 26, 2020
d147ff4
Update Src/FluentAssertions/TypeEnumerableExtensions.cs
jnyrup Apr 26, 2020
9aefb7b
Merge branch 'develop' into develop
fshchudlo Apr 27, 2020
897d8f4
Update Src/FluentAssertions/TypeEnumerableExtensions.cs
fshchudlo Apr 27, 2020
0cdae02
Fix of PR Comments
fshchudlo Apr 27, 2020
e75620d
Fix PR Comments
fshchudlo Apr 27, 2020
8aac3ac
Fix the tests
fshchudlo Apr 27, 2020
ca3a24e
Fix PR comment
fshchudlo Apr 27, 2020
ab08de2
Update approval files
fshchudlo Apr 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ No open-source project is going to be successful without contributions. After we
* The [Pull Request](https://help.github.com/articles/using-pull-requests) is targeted at the `develop` branch.
* The code complies with the [Coding Guidelines for C# 3.0, 4.0 and 5.0](https://csharpcodingguidelines.com/)/.
* The changes are covered by a new or existing set of unit tests which follow the Arrange-Act-Assert syntax such as is used [in this example](https://github.com/fluentassertions/fluentassertions/blob/daaf35b9b59b622c96d0c034e8972a020b2bee55/Tests/FluentAssertions.Shared.Specs/BasicEquivalencySpecs.cs#L33).
* If the contribution affects the documentation, please update [**the documentation**](https://github.com/fluentassertions/fluentassertions/tree/master/docs), which is published on the [website](https://fluentassertions.com/documentation/).
* If the contribution affects the documentation, please update [**the documentation**](https://github.com/fluentassertions/fluentassertions/tree/master/docs), which is published on the [website](https://fluentassertions.com/introduction/).
fshchudlo marked this conversation as resolved.
Show resolved Hide resolved
55 changes: 55 additions & 0 deletions Src/FluentAssertions/TypeEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,60 @@ public static IEnumerable<Type> ThatImplement<T>(this IEnumerable<Type> types)
{
return new TypeSelector(types).ThatImplement<T>();
}

/// <summary>
/// Filters to only include types that are classes.
/// </summary>
public static IEnumerable<Type> ThatAreClasses(this IEnumerable<Type> types)
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
return new TypeSelector(types).ThatAreClasses();
}

/// <summary>
/// Filters to only include types that are not classes.
/// </summary>
public static IEnumerable<Type> ThatAreNotClasses(this IEnumerable<Type> types)
{
return new TypeSelector(types).ThatAreNotClasses();
}
/// <summary>
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// Filters to only include types that are static classes.
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public static IEnumerable<Type> ThatAreStaticClasses(this IEnumerable<Type> types)
{
return new TypeSelector(types).ThatAreStaticClasses();
fshchudlo marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Filters to only include types that are not static classes.
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public static IEnumerable<Type> ThatAreNotStaticClasses(this IEnumerable<Type> types)
{
return new TypeSelector(types).ThatAreNotStaticClasses();
}

/// <summary>
/// Filters to only include types that satisfies the <paramref name="predicate"/> passed.
/// </summary>
public static IEnumerable<Type> ThatAre(this IEnumerable<Type> types, Func<Type, bool> predicate)
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
return new TypeSelector(types).ThatAre(predicate);
}

/// <summary>
/// Returns T for the types which are Task&lt;T&gt;; the type itself otherwise
fshchudlo marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public static IEnumerable<Type> UnwrapTaskTypes(this IEnumerable<Type> types)
{
return new TypeSelector(types).UnwrapTaskTypes();
}

/// <summary>
/// Returns T for the types which are IEnumerable&lt;T&gt; or implement the IEnumerable&lt;T&gt;; the type itself otherwise
/// </summary>
public static IEnumerable<Type> UnwrapEnumerableTypes(this IEnumerable<Type> types)
{
return new TypeSelector(types).UnwrapEnumerableTypes();
}
}
}
11 changes: 11 additions & 0 deletions Src/FluentAssertions/Types/MethodInfoSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ public MethodInfoSelector ThatAreNotDecoratedWithOrInherit<TAttribute>()
return this;
}

/// <summary>
/// Select return types of the methods
/// </summary>
/// <returns></returns>
public TypeSelector ReturnTypes()
dennisdoomen marked this conversation as resolved.
Show resolved Hide resolved
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
var returnTypes = selectedMethods.Select(mi => mi.ReturnType);

return new TypeSelector(returnTypes);
}

/// <summary>
/// The resulting <see cref="MethodInfo"/> objects.
/// </summary>
Expand Down
46 changes: 46 additions & 0 deletions Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,52 @@ public AndConstraint<MethodInfoSelectorAssertions> NotBeDecoratedWith<TAttribute
return new AndConstraint<MethodInfoSelectorAssertions>(this);
}

/// <summary>
/// Asserts that the selected methods have specified <paramref name="accessModifier"/>.
/// </summary>
/// <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 AndConstraint<MethodInfoSelectorAssertions> Be(CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs)
{
var methods = SubjectMethods.Where(pi => pi.GetCSharpAccessModifier() != accessModifier).ToArray();
var message = $"Expected all selected methods to be {accessModifier}{{reason}}, but the following methods are not:" + Environment.NewLine + GetDescriptionsFor(methods);

Execute.Assertion
.ForCondition(!methods.Any())
.BecauseOf(because, becauseArgs)
.FailWith(message);

return new AndConstraint<MethodInfoSelectorAssertions>(this);
}

/// <summary>
/// Asserts that the selected methods don't have specified <paramref name="accessModifier"/>
/// </summary>
/// <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 AndConstraint<MethodInfoSelectorAssertions> NotBe(CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs)
{
var methods = SubjectMethods.Where(pi => pi.GetCSharpAccessModifier() == accessModifier).ToArray();
var message = $"Expected all selected methods to not be {accessModifier}{{reason}}, but the following methods are:" + Environment.NewLine + GetDescriptionsFor(methods);

Execute.Assertion
.ForCondition(!methods.Any())
.BecauseOf(because, becauseArgs)
.FailWith(message);

return new AndConstraint<MethodInfoSelectorAssertions>(this);
}

private MethodInfo[] GetMethodsWithout<TAttribute>(Expression<Func<TAttribute, bool>> isMatchingPredicate)
where TAttribute : Attribute
{
Expand Down
92 changes: 92 additions & 0 deletions Src/FluentAssertions/Types/TypeSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FluentAssertions.Common;

namespace FluentAssertions.Types
Expand Down Expand Up @@ -165,6 +167,96 @@ public TypeSelector ThatAreNotUnderNamespace(string @namespace)
return this;
}

/// <summary>
/// Determines whether the type is class
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public TypeSelector ThatAreClasses()
{
types = types.Where(t => t.IsClass).ToList();
return this;
}
/// <summary>
/// Determines whether the type is not a class
/// </summary>
public TypeSelector ThatAreNotClasses()
{
types = types.Where(t => !t.IsClass).ToList();
return this;
}

/// <summary>
/// Determines whether the type is a static class
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public TypeSelector ThatAreStaticClasses()
{
types = types.Where(t => t.IsCSharpStatic()).ToList();
return this;
}

/// <summary>
/// Determines whether the type is not a static class
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public TypeSelector ThatAreNotStaticClasses()
{
types = types.Where(t => !t.IsCSharpStatic()).ToList();
return this;
}

/// <summary>
/// Allows to filter the types with the <paramref name="predicate"/> passed
/// </summary>
public TypeSelector ThatAre(Func<Type, bool> predicate)
{
types = types.Where(predicate).ToList();
return this;
}

/// <summary>
/// Returns T for the types which are Task&lt;T&gt;; the type itself otherwise
/// </summary>
public TypeSelector UnwrapTaskTypes()
{
types = types.Select(type =>
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
return type.GetGenericArguments().Single();
}
return type == typeof(Task) ? typeof(void) : type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-generic ValueTask should also return void

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}).ToList();

return this;
}

/// <summary>
/// Returns T for the types which are IEnumerable&lt;T&gt; or implement the IEnumerable&lt;T&gt;; the type itself otherwise
/// </summary>
public TypeSelector UnwrapEnumerableTypes()
{
types = types
.Select(type =>
{
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return type.GetGenericArguments().Single();
}

var iEnumerableDefinition = type.GetInterfaces()
.SingleOrDefault(iType => iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IEnumerable<>));
fshchudlo marked this conversation as resolved.
Show resolved Hide resolved
if (iEnumerableDefinition != null)
fshchudlo marked this conversation as resolved.
Show resolved Hide resolved
{
return iEnumerableDefinition.GetGenericArguments().Single();
}
}

return type;
}).ToList();

return this;
}

/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
Expand Down
41 changes: 41 additions & 0 deletions Src/FluentAssertions/Types/TypeSelectorAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,47 @@ public AndConstraint<TypeSelectorAssertions> NotBeDecoratedWithOrInherit<TAttrib
return new AndConstraint<TypeSelectorAssertions>(this);
}

/// <summary>
/// Asserts that the all <see cref="Type"/> are sealed classes
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <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 AndConstraint<TypeSelectorAssertions> BeSealed(string because = "", params object[] becauseArgs)
{
var notSealedTypes = Subject.Where(type => !type.IsCSharpSealed()).ToArray();

Execute.Assertion.ForCondition(!notSealedTypes.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected all types to be sealed{reason}, but the following types are not:{0}{1}.", Environment.NewLine, GetDescriptionsFor(notSealedTypes));

return new AndConstraint<TypeSelectorAssertions>(this);
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Asserts that the all <see cref="Type"/> are not sealed classes
/// </summary>
/// <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 AndConstraint<TypeSelectorAssertions> NotBeSealed(string because = "", params object[] becauseArgs)
{
var sealedTypes = Subject.Where(type => type.IsCSharpSealed()).ToArray();

Execute.Assertion.ForCondition(!sealedTypes.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected all types not to be sealed{reason}, but the following types are:{0}{1}.", Environment.NewLine, GetDescriptionsFor(sealedTypes));

return new AndConstraint<TypeSelectorAssertions>(this);
}
private static string GetDescriptionsFor(IEnumerable<Type> types)
{
string[] descriptions = types.Select(GetDescriptionFor).ToArray();
Expand Down