FunctionalDev.MoqHelpers 3.1.4

dotnet add package FunctionalDev.MoqHelpers --version 3.1.4
NuGet\Install-Package FunctionalDev.MoqHelpers -Version 3.1.4
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FunctionalDev.MoqHelpers" Version="3.1.4" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FunctionalDev.MoqHelpers --version 3.1.4
#r "nuget: FunctionalDev.MoqHelpers, 3.1.4"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install FunctionalDev.MoqHelpers as a Cake Addin
#addin nuget:?package=FunctionalDev.MoqHelpers&version=3.1.4

// Install FunctionalDev.MoqHelpers as a Cake Tool
#tool nuget:?package=FunctionalDev.MoqHelpers&version=3.1.4

FunctionalDev.MoqHelpers

Moq Helpers is a library which has been created to aid unit testing.

Please read through the rest of this document to view tools which can help reduce the brittle nature of unit tests, decrease excess code when setting up objects and enhance the clarity of what it is you're testing.

A particular focus of this library is setting up Mock objects (including non-public members) and providing access to non-public members and methods of instantiated objects.

For a complete changelog, please see the ChangeLog below

Table of Contents

  1. Activators - Creating mock objects with less boilerplate code
    1. Lazy Mock Activation - Create abstract mocks with challenging constructor arguments.
    2. Lazy Activation - Create complex objects without providing constructor arguments.
    3. Container (ActivatedObject) Creation - Encapsulate construction arguments providing access to constructor arguments in unit tests only when required.
      1. ActivatedObject.SetupMock - Inline mock setup.
      2. ActivatedObject.BindMocks - Inline mock binding.
      3. ActivatedObject.AddToArgumentCollection - Inline argument addition.
      4. ActivatedObject.SwitchArgument - Inline switch argument.
  2. Object Proxy - Interact with private/protected members (properties, fields, methods and generic methods)
  3. Examples
  4. Extension Methods
    1. Mock.Setup - Setup mock members with less boilerplate code. Enables the setup of protected methods.
    2. ILogger.Unwrap - Provides resolved ILogger invocations in a simple class.
    3. ILogger.Verify - Provides verify methods on ILogger, to be used when verifying a particular log has been invoked.
    4. IEnumerable{T}.Enumerate - Enumerate a collection without storing the results.
    5. IEnumerable{T}.SingleOrThrow - Throw a custom exception if a single match is not found.
    6. Mock.GetInvocations - Extract type cast arguments from a method invocation on a mock.
    7. Mock.PassThrough - Auto-setup all members (fields, properties and methods) for a mock to a given instance.
    8. Type.TryMatchInterface - Determines if a type inherits from a given interface.
    9. Type.MethodSignatureMatch - Can be used to check if a given set of types can be used to invoke a method signature.
    10. ActivatedObject.SetupOptions - Can be used to setup a IOptions{TConfiguration} instance on a given ActivatedObject.
    11. ActivatedObject.ConvertToInstanceType - Converts from ActivatedObject{Mock{TActivated}} to ActivatedObject{TActivated}.
  5. Full Example
  6. Chaining Calls on ActivatedObject / ActivatedObject{T}
  7. Changelog

Activators

Creating instances of objects is central to most unit testing. Activator classes have been provided to reduce the overhead and brittle nature of setting up classes, when it may not be required to provide all arguments for the constructor, if those arguments are not necessary for the particular unit test in focus.

These activators can be used to future-proof the creation of objects, in that future changes to an object constructor will not break existing unit tests, providing that the additional arguments do not affect existing behaviour.

Lazy Mock Activation

A MockActivator class can be used to create Mock{T} classes.

This activator has CreateLazy and CreateNull methods which allow the creation of Mock{T} classes without needing to provide all/any constructor arguments.

Whilst these methods can be used to provide all arguments, they can also be provided with some or all constructor arguments to enable future-proofing with future constructor signature changes.

Given the following class:

public abstract class Person
{
  protected Person(ILogger<Person> logger) { }
}

The following example uses CreateLazy, without arguments, to create a new instance of Mock{Person} without supplying an instance of ILogger{Person}. Note that the Person base type will be supplied new Mock{ILogger {Person}}.Object as a value for ILogger{Person}.

public Mock<Person> CreateLazy()
  => MockActivator.CreateLazy<Person>();

The following example uses CreateNull, without arguments, to create a new instance of Mock{Person} without supplying an instance of ILogger{Person}. Note that the Person base type will be supplied null as a value for ILogger{Person}.

public Mock<Person> CreateNull()
  => MockActivator.CreateNull<Person>();

The following example uses CreateLazy, with arguments, to create a new instance of Mock{Person} supplying an instance of Mock{ILogger{Person}}. Note that the Person base type will be supplied loggerMock.Object as a value for ILogger{Person}.

public Mock<Person> CreateLazyWithMockArgument(Mock<ILogger<Person>> loggerMock)
  => MockActivator.CreateLazy<Person>(loggerMock);

The following example uses CreateLazy, with arguments, to create a new instance of Mock{Person} supplying an instance of ILogger{Person}. Note that the Person base type will be supplied loggerInstance as a value for ILogger{Person}.

public Mock<Person> CreateLazyWithMockArgument(ILogger<Person> loggerInstance)
  => MockActivator.CreateLazy<Person>(loggerInstance);

Lazy Activation

A LazyActivator class can be used to create instances of concrete classes.

This activator has CreateLazy and CreateNull methods which allow the creation of concrete classes without needing to provide all/any constructor arguments.

Whilst these methods can be used to provide all arguments, they can also be provided with some or all constructor arguments to enable future-proofing with future constructor signature changes.

An example can be seen below:

public class Person
{
  public Person(ILogger<Person> logger) { }
}

The following example uses CreateLazy, without arguments, to create a new instance of Person without supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied new Mock{ILogger{Person}}.Object as a value for ILogger{Person}.

public Person CreateLazy()
  => LazyActivator.CreateLazy<Person>();

The following example uses CreateNull, without arguments, to create a new instance of Person without supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied null as a value for ILogger{Person}.

public Person CreateNull()
  => LazyActivator.CreateNull<Person>();

The following example uses CreateLazy, with arguments, to create a new instance of Person supplying an instance of Mock{ILogger{Person}}. Note that the Person constructor will be supplied loggerMock.Object as a value for ILogger{Person}.

public Person CreateLazyWithMockArgument(Mock<ILogger<Person>> loggerMock)
  => LazyActivator.CreateLazy<Person>(loggerMock);

The following example uses CreateLazy, with arguments, to create a new instance of Person supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied loggerInstance as a value for ILogger{Person}.

public Person CreateLazyWithMockArgument(ILogger<Person> loggerInstance)
  => LazyActivator.CreateLazy<Person>(loggerInstance);

Container Creation

Container Lazy and Mock activation has been added to provide a means of extracted auto loaded construction objects. Please note that whilst this example uses MockActivator this is also present in LazyActivator.

private readonly TestClass _testClass;
private readonly Mock<ILogger<TestClass>> _loggerMock;
private readonly Mock<IWidget> _widgetMock;
private readonly IWidget _widget;

public UnitTest()
{
    var container = MockActivator.CreateLazyContainer<TestClass>();

    _testClass = container.Instance.Object;
    _loggerMock = container.GetArgumentAsMock<ILogger<TestClass>>();
    _widgetMock = container.GetArgumentAsMock<IWidget>();
    _widget = container.GetArgument<IWidget>();
}
ActivatedObject.SetupMock

Setup mock is a chaining method on activated objects to enable inline mock setups.

LazyActivator.CreateLazyContainer<TestClass>()
  .SetupMock<IWidget>(mock => mock.Setup(x => x.Value, "value"));
ActivatedObject.BindMocks

Bind mocks is a chaning method on activated objects to enable inline multi mock setups. For example, when the response from mock B depends on mock A.

LazyActivator.CreateLazyContainer<TestClass>()
  .BindMocks((IFactory factory, IInstance instance) => factory.Setup(x => x.GetInstance, () => instance.Object));
ActivatedObject.AddToArgumentCollection

Add to argument collection is a chaining method on activated objects to enable the addition to the argument collection inline. The following will insert a new instance using MockActivator or LazyActivator, whichever is relevant.

LazyActivator.CreateLazyContainer<TestClass>()
  .AddToArgumentCollection<IWidget>();

The following will insert the given reference.

LazyActivator.CreateLazyContainer<TestClass>()
  .AddToArgumentCollection(new Widget());
ActivatedObject.SwitchArgument

Switch argument is a chaining method on activated objects to enable the switching of arguments in the argument collection, before the object is created (.Instance called).

LazyActivator.CreateLazyContainer<TestClass>()
  .SwitchArgument<IWidget>(new AlternativeWidget());

Object Proxy

The ObjectProxy class can be used to interact (set/get) private members of a given instance, and can be used to invoke protected/private methods.

Dot separated member access is supported to access members of members recursively. E.g. ObjectProxy.For(person)["Name"."FirstName"]

Static classes can be setup with ObjectProxy.ForStatic to interact with static classes.

Examples

Given the following class:

public class Person
{
  private string Name { set; get; }

  private Person(ILogger<Person> logger, string name)
  {
    Name = name;
  }

  private string GetName()
    => Name;

  private string ToStringFor<T>(T arg)
    => arg.ToString();
}

Creating an instance of Person can be achieved as shown below.

var person = LazyActivator.CreateLazy<Person>("Fred");
// or
var person = LazyActivator.CreateLazy<Person>();

And members can be managed as shown below.

// Get.
var name = ObjectProxy.For(person)["Name"];

// Set.
ObjectProxy.For(person)["Name"] = "Bob";

Methods can be invoked via the proxy with:

var result = person.InvokeMethod<string>("GetName");

For full examples please see end of this file.

Extension Methods

Mock.Setup

Several extension methods have been provided which enable functional Moq creation (returning Mock{T} to enable chaining) and to setup private/protected members.

Please note that to resolve the extension methods, it may be required to add the following using statement:

using FunctionalDev.MoqHelpers;

Please also note that the extension methods are not the typical .Setup(...).Returns(...) format. For simple method setups the format this library provides is:

.Setup(type => type.MethodName, Expression<Func<InputArgs, ReturnArg>>);

Given an example interface:

public interface IPerson
{
    string SetName(string name);
    int GetAge();
}

The following can be used to set up an IPerson interface.

var person = new Mock<IPerson>()
    .Setup(x => x.SetName, (string name) => "")
    .Setup(x => x.GetAge, () => 25);

Please note that this does not provide any filters on the arguments for methods (such as It.Is<T>(Func<T, bool>) filtering).

ILogger.Unwrap

Mock{ILogger}.Unwrap() is an extension method which returns a set of resolved invocations on the logger.

This can be used when it is required to manually inspect the invocations of a ILogger Mock.

For example, given the following code snippet:

var loggerMock = new Mock<ILogger<ExampleClass>>();
loggerMock.Object.LogError("Error log example");
loggerMock.Object.LogDebug("Debug log example");

loggerMock.Unwrap().ToList().ForEach(loggerArguments =>
{
    Console.WriteLine($"Level: '{loggerArguments.Level}'");
    Console.WriteLine($"Message: '{loggerArguments.Message}'");
    Console.WriteLine($"Exception: '{loggerArguments.Exception?.Message}'");
    Console.WriteLine();
});

Output:

Level: 'Error'
Message: 'Error log example'
Exception: ''

Level: 'Debug'
Message: 'Debug log example'
Exception: ''

ILogger.Verify

Mock{ILogger{T}}.Verify(...) are a series of extension methods which can be used to verify that an ILogger has been invoked for a particular ErrorLog, Times and message.

A combination of ErrorLog, message text, and Times can be used to ensure that a particular log has been invoked. Additionally, a Func<string, ErrorLog, bool> can be used to have full control over the verification.

IEnumerable{T}.Enumerate

It is sometimes required during unit testing to ensure that an enumerable set has been enumerated (to ensure the collection is valid) before continuing with any assertions, for example the result of a method target. Call IEnumerable{T}.Enumerate() to fully iterate through a given collection without assigning references to any of the items.

IEnumerable{T}.SingleOrThrow

IEnumerable{T}.SingleOrThrow can be used to pull a single object of T from a given collection. If exactly one object is not found then a custom exception is thrown, as provided in the call.

Mock.GetInvocations

Mock.GetInvocations<T1...T10>(string/MethodInfo) can be used to return type cast method invocations from a given mock.

Mock.PassThrough

Mock.PassThrough can be used to auto-setup all members (fields, properties and methods) for a mock to a given instance.

Note that for a mock setup this way it will be possible to override setup methods if required, as well as effectively using a mock to capture invoked members of a given instance.

Type.TryMatchInterface

Type.TryMatchInterface can be used to determine if a given type inherits from a given interface.

Type.MethodSignatureMatch

Type.MethodSignatureMatch can be used to compare two type arrays and checks that the second set can be used to invoke a method whose signature matches the first set.

ActivatedObject.SetupOptions

ActivatedObject.SetupOptions can be used to setup a IOptions{TConfiguration} instance on a given ActivatedObject.

ActivatedObject.ConvertToInstanceType

Converts from ActivatedObject{Mock{TActivated}} to ActivatedObject{TActivated}.

Full Example

Consider the following classes.

public abstract record FullExampleObjectBase(ILogger<FullExampleObjectBase> Logger)
{
    public ILogger<FullExampleObjectBase> Logger { get; } = Logger;

    private int _localIntegerValue = 1;

    protected virtual string GetName()
        => nameof(FullExampleObjectBase);

    private void SetLocalIntegerValue(int newValue)
    {
        _localIntegerValue = newValue;
    }

    private int GetLocalIntegerValue()
        => _localIntegerValue;

    private int GenericMethod<T>() => 1;

    public abstract string GetNameAbstract(string arg);
}

public record FullExampleObject(ILogger<FullExampleObjectBase> Logger)
    : FullExampleObjectBase(Logger)
{
    public override string GetNameAbstract(string arg)
        => arg;
}

public static class FullExampleStaticClass
{
    private static void SetStaticValue(string arg) { }
}

The following test code could be used to interact with these classes.

public class FullExample
{
    public FullExampleObject CreateFullExampleObject()
        => LazyActivator.CreateLazy<FullExampleObject>();

    public FullExampleObject CreateFullExampleObjectWithArgument()
        => LazyActivator.CreateLazy<FullExampleObject>(Mock.Of<ILogger<FullExampleObjectBase>>());

    public void SetPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        proxy["_localIntegerValue"] = 5;
    }

    public void GetPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        var value = (int)proxy["_localIntegerValue"];
    }

    private void CallPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        proxy.InvokeMethod("SetLocalIntegerValue", 5);
    }

    private void CallPrivateWithReturning()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        int resultAsInt = proxy.InvokeMethod<int>("GetLocalIntegerValue");
        object resultAsObject = proxy.InvokeMethod("GetLocalIntegerValue");
    }

    private void CallGenericPrivateMethod()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        int resultAsInt = proxy.InvokeGenericMethod<int>("GenericMethod", new[] { typeof(RecordClass) }, Array.Empty<object>());
        object resultAsObject = proxy.InvokeGenericMethod("GenericMethod", new[] { typeof(RecordClass) }, Array.Empty<object>());
    }

    private void ObjectProxyForStaticClass()
    {
        var proxy = ObjectProxy.ForStatic(typeof(FullExampleStaticClass));
        proxy.InvokeMethod("SetStaticValue", "Hello world");
    }

    private void SetupExtensionMethods()
    {
        MockActivator.CreateLazy<FullExampleObjectBase>()
            // Setting members.
            .Setup(x => x.Logger, Mock.Of<ILogger<FullExampleObjectBase>>())
            // Setting public methods.
            .Setup(x => x.GetNameAbstract, (string arg) => arg + "test")
            // Setting private methods.
            .Setup("GetName", () => "Hello World")
            // Also works for public methods.
            .Setup("GetNameAbstract", (string arg) => arg + "test")
            ;
    }
}

Chaining Calls on ActivatedObject/ActivatedObject{T}

Further changes in containers allows for chaining setups:

var container = LazyActivator.CreateLazyContainer<MyTestClass>()
  // Setup mocks can be used to setup mocks in chaining calls.
  .SetupMock<IWidget>(widget => { })
  .SetupMock<ITestObject>(testObject => testObject.Setup(x => x.Name, "Fred"))
  .SetupMock<ITestObject>(testObject => testObject.Setup(x => x.Name, "Frank"))
  // Add to argument collection can be used to add arguments into the container.
  .AddToArgumentCollection(new Mock<ILogger<MyTestClass>>().Object)
  .SetupMock<ILogger<MyTestClass>>(logger =>
  {
      logger.Setup(...)
  })
  // Bind mocks can be used to setup between mocks.
  .BindMocks<IHttpClient, IHttpClientFactory>((client, factory) => factory.Setup(...));

ChangeLog

3.1.4 | 20/02/2024
  • Fixed a bug in ObjectProxyBase.SetProperty when assigning to a field.
3.1.0 | 21/01/2024
  • Added to ActivatedObject, ActivatedObject{T} and IArgumentProvider: SwitchArgument. Allows substitution of an argument value to another instance.
3.0.25 | 05/12/2023
  • Added to LoggerExtensions the ability to verify by exception in addition to other verification arguments.
3.0.24 | 04/12/2023
  • Added CallbackHelpers.Throw to help with throwing exceptions in setup callback arguments.
3.0.23 | 15/11/2023
  • Added ObjectProxyBase.SetPropertyBehaviour which can be used to configure converting behaviour when assigning properties (defaults false).
  • Added TypeExtensions.GetNonNullableType to use when resolving int? to int or string to string for example.
3.0.22 | 10/10/2023
  • Added Action SequenceBuilder.GenerateSequence(Action[] actionSequence)
  • Added Func<T> SequenceBuilder.GenerateSequence(Func<T>[] funcSequence)
  • Added IActionSequence SequenceBuilder.Build(Action[] actionSequence) (to be used when tracking invocation count)
  • Added IFuncSequence<T> SequenceBuilder.Build(Func<T>[] funcSequence) (to be used when tracking invocation count)
3.0.21 | 06/10/2023
  • Added .SetupMock(Action<IArgumentProvider,Mock<TMocked>>) to ActivatedObject and ActivatedObject{}.
  • Added SequenceBuilder.GenerateSequence.
3.0.20 | 07/08/2023
  • Updated ObjectProxy selection logic for members and methods to include base types considering read and write permissions in the inheritance tree
3.0.19 | 31/07/2023
  • Updated readme
3.0.18 | 31/07/2023
  • Added a common GetInstance method to ActivatedObjectBase
  • Fixed MockActivator.CreateMock(Type,ConstructorOptions,bool,Array{Object}) throwing when attempting to cast ActivatedObject`1 to ActivatedObject
3.0.17 | 27/06/2023
  • Extended logging verify calls to handle event IDs.
  • Included additional information in logger unwrap.
3.0.16 | 02/06/2023
  • Further fix for a bug in Mock.PassThrough where an implementation has methods or properties which do not exist on the type being mocked
3.0.15 | 02/06/2023
  • Fixed a bug in Mock.PassThrough where an implementation has methods or properties which do not exist on the type being mocked
3.0.14 | 15/05/2023
  • Added strict construction methods to LazyActivator and MockActivator
3.0.13 | 18/04/2023
  • Additional fix for bug with ActivatedObject.SetupMock when the mock type is a mocked abstract.
3.0.12 | 18/04/2023
  • Fixed bug with ActivatedObject.SetupMock when the mock type is a mocked abstract.
  • Added ActivatedObject{T}.BindMocks and ActivatedObject.BindMocks
  • Added ActivatedObject{T}.AddToArgumentCollection and ActivatedObject.AddToArgumentCollection
3.0.11 | 17/04/2023
  • Added SetupOptions
3.0.10 | 14/04/2023
  • Rewrote ActivatedObject to only create the object once ActivatedObject.Instance has been invoked (deferred loading). Allows for the arguments, if required, to be configured before the constructor is called..
3.0.9 | 14/04/2023
  • Added extensions to readme
3.0.8 | 14/04/2023
  • Fixed readme TOC
3.0.7 | 14/04/2023
  • Fixed ActivatedObject.GetArgument<TClass>() returning null.
3.0.6 | 17/02/2023
  • Added ConvertToInstanceType extension to convert from ActivatedObject<Mock<T>> to ActivatedObject<T>.
3.0.5 | 16/02/2023
  • Fixed a bug in Mock<ILogger>.Verify() where a particular execution path recursively calls itself and causes a stack overflow.
3.0.4 | 07/02/2023
  • Fixed a bug in Mock<ILogger>.Unwrap() where a non Log method invocation has occurred.
3.0.3 | 07/02/2023
  • Added support for .NET 6
3.0.2 | 24/01/2023
  • Reversed previous obsolete flag additions
3.0.1 | 11/01/2023
  • Introduced ChangeLog
3.0.0 | 11/01/2023
  • Breaking - ActivatedObject{T} no longer inherits from ActivatedObject, instead a new base class has been introduced (ActivatedObjectBase) which they both share.
  • Breaking - The two methods GetArgument and GetArgumentAsMock have been marked as obsolete (and have been moved to ActivatedObjectBase) and may be removed on the next major release.
  • Breaking - ActivatedObject no longer has a property called InstanceUnTyped it has been renamed to Instance with a type of object, mirroring the property on ActivatedObject{T} named Instance of type T (class generic type).
  • Added chaining calls to ActivatedObjects to enable setting up of mocks inline/chained.
  • Rewrite on the argument logic allowing custom injection of objects after instantiation.
  • Updated all ILogger{T} extension methods to also support ILogger.
  • Logger/Logger{T}.Unwrap() now returns the exception, if provided on invocations.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.1.4 134 2/20/2024
3.1.3 120 1/22/2024
3.1.2 68 1/22/2024
3.1.1 81 1/21/2024
3.1.0 82 1/21/2024
3.0.25 196 12/6/2023
3.0.24 117 12/4/2023
3.0.23 144 11/15/2023
3.0.22 173 10/10/2023
3.0.21 123 10/6/2023
3.0.20 188 8/7/2023
3.0.19 132 7/31/2023
3.0.18 131 7/31/2023
3.0.17 157 6/27/2023
3.0.16 165 6/2/2023
3.0.15 124 6/2/2023
3.0.14 153 5/15/2023
3.0.13 233 4/18/2023
3.0.12 162 4/18/2023
3.0.11 171 4/17/2023
3.0.10 184 4/14/2023
3.0.9 165 4/14/2023
3.0.8 176 4/14/2023
3.0.7 167 4/14/2023
3.0.6 264 2/17/2023
3.0.5 217 2/16/2023
3.0.4 242 2/9/2023
3.0.3 242 2/7/2023
3.0.2 1,407 1/24/2023
3.0.1 335 1/11/2023
3.0.0 286 1/11/2023
2.1.4 456 11/1/2022
2.1.3 509 7/19/2022
2.1.2 467 5/3/2022
2.1.1 422 5/3/2022
2.1.0 414 4/27/2022
2.0.15 405 4/26/2022
2.0.14 457 3/10/2022
2.0.13 716 1/27/2022
2.0.12 638 1/24/2022
2.0.11 630 1/21/2022
2.0.10 402 9/10/2021
2.0.9 340 9/10/2021
2.0.8 288 9/3/2021
2.0.7 292 9/3/2021
2.0.6 328 9/2/2021
2.0.5 287 8/27/2021
2.0.4 283 8/27/2021
2.0.3 286 8/13/2021
2.0.2 329 7/29/2021
2.0.1 327 7/28/2021
2.0.0 313 7/28/2021
1.1.0 358 7/28/2021
1.0.0 350 7/27/2021