Skip to content

Commit

Permalink
feat: support nullable IFileSystemInfo assertions (#31)
Browse files Browse the repository at this point in the history
Make IDirectoryInfo and IFileInfo nullable in the assertions, so that
also `null` can be asserted.

---------

Co-authored-by: Valentin Breuß <v.breuss@tig.at>
  • Loading branch information
vbreuss and vbtig committed Aug 21, 2023
1 parent cc7eeb3 commit 94f42c4
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
/// Assertions on <see cref="IDirectoryInfo" />.
/// </summary>
public class DirectoryAssertions :
ReferenceTypeAssertions<IDirectoryInfo, DirectoryAssertions>
ReferenceTypeAssertions<IDirectoryInfo?, DirectoryAssertions>
{
/// <inheritdoc cref="ReferenceTypeAssertions{TSubject,TAssertions}.Identifier" />
protected override string Identifier => "directory";

internal DirectoryAssertions(IDirectoryInfo instance)
internal DirectoryAssertions(IDirectoryInfo? instance)
: base(instance)
{
}
Expand All @@ -19,19 +19,30 @@ internal DirectoryAssertions(IDirectoryInfo instance)
/// </summary>
public AndConstraint<DirectoryAssertions> HasFileMatching(
string searchPattern = "*", string because = "", params object[] becauseArgs)
=> HasFilesMatching(searchPattern, 1, because, becauseArgs);

/// <summary>
/// Asserts that the current directory has at least <paramref name="minimumCount"/> files which match the <paramref name="searchPattern" />.
/// </summary>
public AndConstraint<DirectoryAssertions> HasFilesMatching(
string searchPattern = "*", int minimumCount = 1, string because = "", params object[] becauseArgs)
{
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.ForCondition(Subject != null)
.FailWith(
"You can't assert a directory having files if the DirectoryInfo is null")
.Then
.ForCondition(!string.IsNullOrEmpty(searchPattern))
.FailWith(
"You can't assert a file exist in the directory if you don't pass a proper name")
"You can't assert a directory having files if you don't pass a proper name")
.Then
.Given(() => Subject.GetFiles(searchPattern))
.ForCondition(fileInfos => fileInfos.Length > 0)
.Given(() => Subject!)
.ForCondition(directoryInfo => directoryInfo.GetFiles(searchPattern).Length > 0)
.FailWith(
"Expected {context} {1} to contain at least one file matching {0}{reason}, but none was found.",
_ => searchPattern, _ => Subject.Name);
$"Expected {{context}} {{1}} to contain at least {(minimumCount == 1 ? "one file" : $"{minimumCount} files")} matching {{0}}{{reason}}, but none was found.",
_ => searchPattern, directoryInfo => directoryInfo.Name);

return new AndConstraint<DirectoryAssertions>(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class DirectoryInfoAssertions :
/// <inheritdoc cref="ReferenceTypeAssertions{TSubject,TAssertions}.Identifier" />
protected override string Identifier => "directory";

internal DirectoryInfoAssertions(IDirectoryInfo instance)
internal DirectoryInfoAssertions(IDirectoryInfo? instance)
: base(instance)
{
}
Expand Down
44 changes: 32 additions & 12 deletions Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ namespace Testably.Abstractions.FluentAssertions;
/// Assertions on <see cref="IFileInfo" />.
/// </summary>
public class FileAssertions :
ReferenceTypeAssertions<IFileInfo, FileAssertions>
ReferenceTypeAssertions<IFileInfo?, FileAssertions>
{
/// <inheritdoc cref="ReferenceTypeAssertions{TSubject,TAssertions}.Identifier" />
protected override string Identifier => "file";

internal FileAssertions(IFileInfo instance)
internal FileAssertions(IFileInfo? instance)
: base(instance)
{
}
Expand All @@ -26,13 +26,17 @@ internal FileAssertions(IFileInfo instance)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith(
"You can't assert the content of a file if the FileInfo is null")
.Then
.Given(() => Subject!)
.ForCondition(fileInfo => fileInfo.FileSystem.File
.ReadAllBytes(fileInfo.FullName)
.SequenceEqual(bytes))
.FailWith(
"Expected {context} {0} to match '{1}'{reason}, but it did not.",
_ => Subject.Name, _ => bytes);
fileInfo => fileInfo.Name, _ => bytes);

return new AndConstraint<FileAssertions>(this);
}
Expand All @@ -46,12 +50,16 @@ internal FileAssertions(IFileInfo instance)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith(
"You can't assert the content of a file if the FileInfo is null")
.Then
.Given(() => Subject!)
.ForCondition(fileInfo => pattern.Matches(
fileInfo.FileSystem.File.ReadAllText(fileInfo.FullName)))
.FailWith(
"Expected {context} {0} to match '{1}'{reason}, but it did not.",
_ => Subject.Name, _ => pattern);
fileInfo => fileInfo.Name, _ => pattern);

return new AndConstraint<FileAssertions>(this);
}
Expand All @@ -66,12 +74,16 @@ internal FileAssertions(IFileInfo instance)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith(
"You can't assert the content of a file if the FileInfo is null")
.Then
.Given(() => Subject!)
.ForCondition(fileInfo => pattern.Matches(
fileInfo.FileSystem.File.ReadAllText(fileInfo.FullName, encoding)))
.FailWith(
"Expected {context} {0} to match '{1}'{reason}, but it did not.",
_ => Subject.Name, _ => pattern);
fileInfo => fileInfo.Name, _ => pattern);

return new AndConstraint<FileAssertions>(this);
}
Expand All @@ -85,11 +97,15 @@ internal FileAssertions(IFileInfo instance)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith(
"You can't assert that the file is not read-only if the FileInfo is null")
.Then
.Given(() => Subject!)
.ForCondition(fileInfo => !fileInfo.IsReadOnly)
.FailWith(
"Expected {context} {0} not to be read-only{reason}, but it was.",
_ => Subject.Name);
fileInfo => fileInfo.Name);

return new AndConstraint<FileAssertions>(this);
}
Expand All @@ -103,11 +119,15 @@ internal FileAssertions(IFileInfo instance)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith(
"You can't assert that the file is read-only if the FileInfo is null")
.Then
.Given(() => Subject!)
.ForCondition(fileInfo => fileInfo.IsReadOnly)
.FailWith(
"Expected {context} {0} to be read-only{reason}, but it was not.",
_ => Subject.Name);
fileInfo => fileInfo.Name);

return new AndConstraint<FileAssertions>(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class FileInfoAssertions :
/// <inheritdoc cref="ReferenceTypeAssertions{TSubject,TAssertions}.Identifier" />
protected override string Identifier => "file";

internal FileInfoAssertions(IFileInfo instance)
internal FileInfoAssertions(IFileInfo? instance)
: base(instance)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ public static class FileSystemExtensions
/// Returns a <see cref="DirectoryAssertions" /> object that can be used to
/// assert the current <see cref="IDirectoryInfo" />.
/// </summary>
public static DirectoryInfoAssertions Should(this IDirectoryInfo instance)
public static DirectoryInfoAssertions Should(this IDirectoryInfo? instance)
=> new(instance);

/// <summary>
/// Returns a <see cref="FileAssertions" /> object that can be used to
/// assert the current <see cref="IFileInfo" />.
/// </summary>
public static FileInfoAssertions Should(this IFileInfo instance)
public static FileInfoAssertions Should(this IFileInfo? instance)
=> new(instance);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
/// </summary>
public abstract class
FileSystemInfoAssertions<TFileSystemInfo, TAssertion>
: ReferenceTypeAssertions<TFileSystemInfo, TAssertion>
: ReferenceTypeAssertions<TFileSystemInfo?, TAssertion>
where TFileSystemInfo : IFileSystemInfo
where TAssertion : ReferenceTypeAssertions<TFileSystemInfo, TAssertion>
where TAssertion : ReferenceTypeAssertions<TFileSystemInfo?, TAssertion>
{
/// <summary>
/// Initializes a new instance of <see cref="FileSystemInfoAssertions{TFileSystemInfo,TAssertion}" />
/// </summary>
protected FileSystemInfoAssertions(TFileSystemInfo subject) : base(subject)
protected FileSystemInfoAssertions(TFileSystemInfo? subject) : base(subject)
{
}

Expand All @@ -25,12 +25,15 @@ protected FileSystemInfoAssertions(TFileSystemInfo subject) : base(subject)
Execute.Assertion
.WithDefaultIdentifier(Identifier)
.BecauseOf(because, becauseArgs)
.Given(() => Subject)
.ForCondition(Subject != null)
.FailWith("You can't assert that the {context} exists if it is null")
.Then
.Given(() => Subject!)
.ForCondition(fileSystemInfo => fileSystemInfo.Exists)
.FailWith(
"Expected {context} {0} to exist{reason}, but it did not.",
_ => Subject.Name);
fileSystemInfo => fileSystemInfo.Name);

return new AndConstraint<TFileSystemInfo>(Subject);
return new AndConstraint<TFileSystemInfo>(Subject!);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ public class DirectoryInfoAssertionsTests
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void HaveFileMatching_Null_ShouldThrow(string because)
{
IDirectoryInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().HaveFileMatching(because: because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void HaveFileMatching_WithMatchingFile_ShouldNotThrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ namespace Testably.Abstractions.FluentAssertions.Tests;

public class FileInfoAssertionsTests
{
[Theory]
[AutoData]
public void BeReadOnly_Null_ShouldThrow(string because)
{
IFileInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().BeReadOnly(because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void BeReadOnly_WithReadOnlyFile_ShouldNotThrow(FileDescription fileDescription)
Expand Down Expand Up @@ -61,6 +77,22 @@ public void BeReadOnly_WithReadOnlyFile_ShouldNotThrow(FileDescription fileDescr
sut.Should().HaveContent(bytes);
}

[Theory]
[AutoData]
public void HaveContent_Bytes_Null_ShouldThrow(byte[] bytes, string because)
{
IFileInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().HaveContent(bytes, because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void HaveContent_Bytes_OnlyPartOfContent_ShouldNotThrow(
Expand Down Expand Up @@ -181,6 +213,39 @@ public void BeReadOnly_WithReadOnlyFile_ShouldNotThrow(FileDescription fileDescr
sut.Should().HaveContent(pattern);
}

[Theory]
[AutoData]
public void HaveContent_StringContent_Null_ShouldThrow(string content, string because)
{
IFileInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().HaveContent(content, because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void HaveContent_StringContent_WithEncoding_Null_ShouldThrow(
Encoding encoding, string content, string because)
{
IFileInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().HaveContent(content, encoding, because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void HaveContent_WithEncodingMismatch_ShouldThrow(
Expand All @@ -204,6 +269,22 @@ public void BeReadOnly_WithReadOnlyFile_ShouldNotThrow(FileDescription fileDescr
$"Expected file \"{fileName}\" to match '{pattern}' {because}, but it did not.");
}

[Theory]
[AutoData]
public void NotBeReadOnly_Null_ShouldThrow(string because)
{
IFileInfo? sut = null;

Exception? exception = Record.Exception(() =>
{
sut.Should().NotBeReadOnly(because);
});

exception.Should().NotBeNull();
exception!.Message.Should().Contain("null");
exception.Message.Should().NotContain(because);
}

[Theory]
[AutoData]
public void NotBeReadOnly_WithReadOnlyFile_ShouldThrow(
Expand Down
Loading

0 comments on commit 94f42c4

Please sign in to comment.