Customization values
You can add your own customizations on top of the AwexpectCustomization
class by adding extension methods.
Namespaces used on this page
using System.Reflection;
using aweXpect.Core;
using aweXpect.Core.Extending;
using aweXpect.Customization;
Simple values
You can add a simple customizable value (e.g. an int):
public static class PlaylistCustomizationExtensions
{
public static ICustomizationValueSetter<int> MaximumPlaylistLength(
this AwexpectCustomization awexpectCustomization)
=> new CustomizationValue<int>(awexpectCustomization, "MusicStore.MaximumPlaylistLength", 100);
}
CustomizationValue<TValue> from aweXpect.Customization stores the value under the key. The key identifies the
value, so choose one that no other package uses, e.g. prefixed with the name of your package. A null that was set is
returned as null, not as the default value, so pass a validate callback that rejects null if your value must not
be null, as the group below does.
This allows expectations to access the value:
// will return the default value of 100
int maximumPlaylistLength = Customize.aweXpect.MaximumPlaylistLength().Get();
And users can customize the value:
using (Customize.aweXpect.MaximumPlaylistLength().Set(50))
{
// will now return 50
int maximumPlaylistLength = Customize.aweXpect.MaximumPlaylistLength().Get();
}
// will now return again the default value of 100, because the customization lifetime was disposed
_ = Customize.aweXpect.MaximumPlaylistLength().Get();
You can also use this mechanism for complex objects like classes, but then they can only be changed as a whole and not property by property.
List values
For a list, use CustomizationListValue<TItem> instead of a CustomizationValue<IReadOnlyList<TItem>>:
public static class TrackTitleCustomizationExtensions
{
public static ICustomizationValueSetter<IReadOnlyList<string>> IgnoredTrackTitles(
this AwexpectCustomization awexpectCustomization)
=> new CustomizationListValue<string>(awexpectCustomization, "MusicStore.IgnoredTrackTitles",
Array.AsReadOnly<string>(["Intro", "Outro"]));
}
Set stores a read-only copy of the list and rejects null. Otherwise a caller who keeps the list they set, or who
casts the returned list to a mutable one, could change the value outside its scope. The default value is returned as
it is, so pass a read-only list. A validate callback receives the copy, e.g. to reject null items.
Customization groups
To offer several related values, add a group: a class that exposes one ICustomizationValueSetter<TValue> per value.
Each value is stored under its own key, so it can be set and restored independently of the other values of the group:
public static class MusicStoreCustomizationExtensions
{
public static MusicStoreCustomization MusicStore(this AwexpectCustomization awexpectCustomization)
=> new(awexpectCustomization);
public class MusicStoreCustomization
{
internal MusicStoreCustomization(IAwexpectCustomization awexpectCustomization)
{
MaximumRadioDuration = new CustomizationValue<TimeSpan>(
awexpectCustomization,
"MusicStore.MaximumRadioDuration",
TimeSpan.FromMinutes(3));
DefaultGenre = new CustomizationValue<string>(
awexpectCustomization,
"MusicStore.DefaultGenre",
"Rock",
value =>
{
if (value is null)
{
throw Tracing.WriteException(
new ArgumentNullException(nameof(value), "The 'value' cannot be null."));
}
});
}
public ICustomizationValueSetter<TimeSpan> MaximumRadioDuration { get; }
public ICustomizationValueSetter<string> DefaultGenre { get; }
}
}
The validate callback checks a value before Set stores it, so that Set(null) throws and changes nothing.
Expectations access each value on its own:
// will return the default value of 3 minutes
TimeSpan maximumRadioDuration = Customize.aweXpect.MusicStore().MaximumRadioDuration.Get();
And users customize each value on its own:
using (Customize.aweXpect.MusicStore().DefaultGenre.Set("Pop"))
{
// will use "Pop" as the default genre
// but keep any configured maximum radio duration
}
Both kinds of customizations work with global defaults
without any change: Customize.aweXpect.Global.MaximumPlaylistLength().Set(50) or
Customize.aweXpect.Global.MusicStore().DefaultGenre.Set(…) stores the value for all async flows, because
Global is an AwexpectCustomization as well. Their
lifetimes behave like the ones of the built-in
values, also when they are disposed out of order.
Values in a built-in group
The built-in groups Equivalency(), Formatting() and Settings() implement IAwexpectCustomization as well, so
you can add a value to one of them with an extension method on the group:
public static class TrackFormattingCustomizationExtensions
{
public static ICustomizationValueSetter<string> TrackDurationFormat(
this AwexpectCustomization.FormattingCustomization formatting)
=> new CustomizationValue<string>(formatting, "MusicStore.Formatting.TrackDurationFormat", @"m\:ss");
}
Users then find the value next to the built-in ones, e.g. Customize.aweXpect.Formatting().TrackDurationFormat(). A
group stores its values in the customization it was taken from, so
Customize.aweXpect.Global.Formatting().TrackDurationFormat().Set(…) stores the value for all async flows.
Excluded assemblies
If your extension scans assemblies, skip the ones that the user excluded with
ExcludedAssemblyPrefixes. AssemblyPrefixes.IsExcluded from
aweXpect.Core.Extending matches an assembly name with the same rule as aweXpect, at a segment boundary of the name:
public static class CatalogAssemblyScanner
{
public static IEnumerable<Assembly> ScannedAssemblies()
{
IReadOnlyList<string> prefixes = Customize.aweXpect.Settings().ExcludedAssemblyPrefixes.Get();
return AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !AssemblyPrefixes.IsExcluded(assembly.GetName().Name, prefixes));
}
}
The overload without prefixes, e.g. AssemblyPrefixes.IsExcluded("System.Text"), reads the current setting on each
call, so read the prefixes once when you check many names.