Skip to content
/ Green Public

Green is a standalone .NET library focused on applying Boolean expressions to target data. Ask better questions of your objects using checks and expectations!

License

Notifications You must be signed in to change notification settings

bwatts/Green

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Green


Green is a standalone .NET Standard 2.1 library focused on applying Boolean functions to target data.

Ask better questions with checks and expectations!

Purpose

Green is inspired by assertions in automated tests. Testing is fundamentally about asking questions, yet asking questions is not only for testing. We deserve to wield these concepts in shipped code.

That said, expectations are really useful in tests.

Contents

1. Quick Start

1.1. Install

Green is a .NET Standard 2.1 package available on NuGet:

dotnet add package Green

It is under the MIT license and, like knowledge, wants to be free.

1.2. Add to the local scope

using static Green.Local;

2. Core Features

2.1. Checks

A check applies a Boolean function to a target value:

if(Check(ch).IsDigit())
{
  // ...
}

Checks compose and defer application until evaluated:

Check<string> check = Check(str).StartsWith("A").HasLength(6);

if(-condition-)
{
  check = check.EndsWith("Z");
}

if(check)
{
  // ...
}

The if statement takes advantage of an implicit conversion to Boolean:

if(Check(n).IsPositive().IsEven() || Check(ch).IsLetter().IsLower())

2.2. Expectations

An expectation applies a Boolean function to a target value and throws an exception if not met:

try
{
  Expect("ABC").StartsWith("B");
}
catch(ExpectException x)
{
  Console.WriteLine(x);
}

Messages format parameter names and runtime values in both debug and release builds:

Unexpected value: Expect("ABC").StartsWith(value: "B")

at Green.Expectable.StartsWith(Expect`1 expect, String value, Issue`1 issue)
at YourProject.BadCode() in C:\YourProject\BadCode.cs:line 91
at YourProject.UsesBadCode() in C:\YourProject\UsesBadCode.cs:line 127
at YourProject.Tests.FailingTest() in C:\YourProject\Tests.cs:line 42

Expectations compose but run immediately:

Expect("ABC").StartsWith("A").EndsWith("Z");

Unexpected value: Expect("ABC").EndsWith(value: "Z")

They also support custom messages:

Expect(wakeupCall.Hours).IsAtLeast(acceptableTime, hours => $"{hours}am is too early")

6am is too early: Expect(6).IsAtLeast(minimum: 8)

It is also possible to expect exceptions:

ExpectThrows<CustomException>(() => {});

Unexpected success: ExpectThrows<CustomException>(<BadCode>b__1_0)

2.3. Collections

Assertion libraries struggle to ask questions of plural data. Sequences and dictionaries get some attention, but the operators and abstractions often feel superficial and of limited use. Workarounds generally involve breaking down the data structure and testing individual pieces.

Green provides comprehensive checks and expectations for collections. The Many suffix denotes API surfaces for sequences and dictionaries:

CheckMany(sequence).HasSameInOrder(other)
CheckMany(dictionary).HasKeys(keys).HasValues(values)

Particularly useful are Has1 through Has8, which pass corresponding items for further inspection:

CheckMany(sequence).Has2((item0, item1) =>
  Check(item0).IsCloseTo(item1, precision: 0.01))

Nested expectations become inner exceptions:

ExpectMany(sequence).Has2((item0, item1) =>
{
  Expect(item0).IsGreaterThan(20);
  Expect(item1).IsNegative().IsNotMinValue();
});
Unexpected value: ExpectMany([1, 5]).Has2(expectItems: <BadCode>b__2_0)
---- Unexpected value: Expect(1).IsGreaterThan(value: 20)
   at Green.Expectable.IsGreaterThan[T](Expect`1 expect, T value, Issue`1 issue)
   at YourProject.BadCode() in C:\YourProject\BadCode.cs:line 93
   at YourProject.UsesBadCode() in C:\YourProject\UsesBadCode.cs:line 127
   at YourProject.Tests.FailingTest() in C:\YourProject\Tests.cs:line 42

--- End of inner exception stack trace ---
   at Green.Expectable.Has2[T](ExpectMany`1 expect, Action`2 expectItems, IssueMany`1 issue)
   at YourProject.BadCode() in C:\YourProject\BadCode.cs:line 91
   at YourProject.UsesBadCode() in C:\YourProject\UsesBadCode.cs:line 127
   at YourProject.Tests.FailingTest() in C:\YourProject\Tests.cs:line 42

2.4. Not

Thus far true has meant success. The Not suffix expects false from every operator:

CheckNot('A').IsDigit().IsUpper()

This is equivalent to !(IsDigit || IsUpper). 'A' is not a digit, but is uppercase, so the result is false.

Checks also support a unary Not which flips the expected result:

Check<char> lowercaseLetter = Check('A').IsLetter().IsLower();
Check<char> notLowercaseLetter = check.Not();

if(lowercaseLetter)    // false
if(notLowercaseLetter) // true

Expectations also work with Not suffix:

ExpectNot('A').IsDigit().IsUpper()

Unexpected value: ExpectNot('A').IsUpper()

However, because expectations run immediately, there is no unary Not.

2.5. Extensibility

Green has extensibility in its DNA. All checks and expectations use the same mechanisms available to consumers.

For example, this is the definition of the IsNull check:

public static Check<T> IsNull<T>(this Check<T> check) where T : class =>
  check.That(t => t == null);

The corresponding IsNull expectation includes an optional Issue<T> delegate that provides a message:

public static Expect<T> IsNull<T>(this Expect<T> expect, Issue<T>? issue = null) where T : class =>
  expect.That(t => t == null, issue.Operator());

The Operator extension method tells Green this stack frame is an operator, enabling the formatted messages.

Expectations with parameters provide the runtime arguments for formatting:

public static Expect<double> IsCloseTo(this Expect<double> expect, double value, double precision, Issue<double>? issue = null) =>
  expect.That(t => Math.Abs(t - value) <= precision, issue.Operator(value, precision));

NOTE: Green opts into nullable reference types. This does not affect consumers but is valuable in other opted-in codebases.

3. Check Types

3.1. Check

This static class is the factory for all check types:

public static Check<T> That<T>(T target);
public static Check<T> Not<T>(T target);

public static CheckMany<T> Many<T>(IEnumerable<T> target)
public static CheckMany<T> ManyNot<T>(IEnumerable<T> target)

public static CheckMany<TKey, TValue> Many<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> target)
public static CheckMany<TKey, TValue> ManyNot<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> target)

Its methods are available in the local scope:

Static method Local method
That Check
Not CheckNot
Many CheckMany
ManyNot CheckManyNot

Applies one or more checks to a target value:

public T Target { get; }
public Check<T> That(Func<T, bool> next)
public Check<T> Not(Func<T, bool> next)
public Check<T> Not()
public bool Apply()

public static implicit operator bool(Check<T> check)
public static implicit operator bool?(Check<T> check)

Applies one or more checks to a target sequence:

public IEnumerable<T> Target { get; }
public CheckMany<T> That(Func<IEnumerable<T>, bool> next)
public CheckMany<T> Not(Func<IEnumerable<T>, bool> next)
public CheckMany<T> Not()
public bool Apply()

public static implicit operator bool(CheckMany<T> check)
public static implicit operator bool?(CheckMany<T> check)

Applies one or more checks to a target dictionary:

public IEnumerable<KeyValuePair<TKey, TValue> Target { get; }
public CheckMany<TKey, TValue> That(Func<IEnumerable<KeyValuePair<TKey, TValue>>, bool> next)
public CheckMany<TKey, TValue> Not(Func<IEnumerable<KeyValuePair<TKey, TValue>>, bool> next)
public CheckMany<TKey, TValue> Not()
public bool Apply()

public static implicit operator bool(CheckMany<TKey, TValue> check)
public static implicit operator bool?(CheckMany<TKey, TValue> check)

4. Expectation Types

4.1. Expect

This static class is the factory for all expectation types:

public static Expect<T> That<T>(T target)
public static Expect<T> Not<T>(T target)

public static ExpectMany<T> Many<T>(IEnumerable<T> target)
public static ExpectMany<T> ManyNot<T>(IEnumerable<T> target)

public static ExpectMany<TKey, TValue> Many<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> target)
public static ExpectMany<TKey, TValue> ManyNot<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> target)

public static void Throws(Action target, Issue<Action>? issue = null)
public static void Throws(Func<object> target, Issue<Func<object>>? issue = null)

public static void Throws<TException>(Action target, Issue<Action>? issue = null) where TException : Exception
public static void Throws<TException>(Func<object> target, Issue<Func<object>>? issue = null) where TException : Exception

public static Task ThrowsAsync<TException>(Func<Task> target, Issue<Func<Task>>? issue = null) where TException : Exception
public static Task ThrowsAsync(Func<Task> target, Issue<Func<Task>>? issue = null)

Its methods are available in the local scope:

Static method Local method
That Expect
Not ExpectNot
Many ExpectMany
ManyNot ExpectManyNot
Throws ExpectThrows
ThrowsAsync ExpectThrowsAsync

Applies a check to a target value and throws ExpectException if not met:

public T Target { get; }
public Expect<T> That(Func<T, bool> check)

public static implicit operator bool(Expect<T> _) => true;
public static implicit operator bool?(Expect<T> _) => true;

NOTES

  • The implicit operators return true for use in expressions as well as statements.
  • Expectations lack a composable Not method. See the Not section for more information.

Applies a check to a target sequence and throws ExpectException if not met:

public IEnumerable<T> Target { get; }
public ExpectMany<T> That(Func<IEnumerable<T>, bool> check)

public static implicit operator bool(ExpectMany<T> _) => true;
public static implicit operator bool?(ExpectMany<T> _) => true;

NOTES

  • The implicit operators return true for use in expressions as well as statements.
  • Expectations lack a composable Not method. See the Not section for more information.

Applies a check to a target dictionary and throws ExpectException if not met:

public IEnumerable<KeyValuePair<TKey, TValue> Target { get; }
public ExpectMany<TKey, TValue> That(Func<IEnumerable<KeyValuePair<TKey, TValue>>, bool> next)

public static implicit operator bool(ExpectMany<TKey, TValue> _) => true;
public static implicit operator bool?(ExpectMany<TKey, TValue> _) => true;

NOTES

  • The implicit operators return true for use in expressions as well as statements.
  • Expectations lack a composable Not method. See the Not section for more information.

5. Issue Types

Formats a message for a target value that did not meet expectations:

public delegate IssueResult Issue<T>(T target);

Formats a message for a target sequence that did not meet expectations:

public delegate IssueResult IssueMany<T>(IEnumerable<T> target);

Formats a message for a target dictionary that did not meet expectations:

public delegate IssueResult IssueMany<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> target);

The result of formatting a message for a target value that did not meet expectations:

public string Message { get; }
public string StackTrace { get; }
public IssueMethod? Method { get; }
public IssueResult? Outer { get; }

public string ToMessage(string target, bool expectedResult)
public ExpectException ToException(string target, bool expectedResult = true, Exception? inner = null)
public ExpectException ToThrowsException(string target, Type exceptionType, Exception? inner = null)
public ExpectException ToThrowsAsyncException(string target, Type exceptionType, Exception? inner = null)

public static implicit operator IssueResult(string userMessage)

public static IssueResult Default(IssueResult? outer = null)
public static IssueResult Default(string stackTrace, IssueResult? outer = null)
public static IssueResult Operator(string stackTrace, string method, string args, IssueResult? outer = null)
public static IssueResult OperatorMany(string stackTrace, string method, string args, IssueResult? outer = null)

public class IssueMethod
{
  public string Name { get; }
  public string Args { get; }
  public bool IsMany { get; }

  public string FormatCall(string target, bool expectedResult)
}

6. Operators

See operators.md for a full list of check, expectation, and issue operators.

About

Green is a standalone .NET library focused on applying Boolean expressions to target data. Ask better questions of your objects using checks and expectations!

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Languages