WebSharper documentation
C# Support

Unit testing in C#

See the main documentation of the WebSharper.Testing library for F# here. C# has all the same features, but the API is slightly different. Test categories are classes, cases are marked with attributes, and a code generator is used to run all the tests in the project.

Categories and tests

TestCategory

TestCategory is a class which you can inherit from to create a class that houses a number of tests. It has many methods that you can use in your test methods to define assertions. Optionally, you can add a Test attribute with a string argument to name the category (default name is the class' name).

Example:

[Test("My tests")]
public class MyTests : TestCategory {
    [Test("Equality")]
    public static void Equality() {
        Equal(1, 1);
    }
}

Test

The Test attribute can be used on methods too, to mark them as a test case. Optionally, you can provide a string argument to name the test (default name is the method's name). The method must follow these rules:

  • It must be returning either void or Task.
  • It must be an instance method. A new instance of the containing class will be created for running each test.
  • It can have none or a single parameter, in the latter case, random values will be created for it based on the type of the parameter. Supported types are int, double, bool, string, object and also tuples, arrays, IEnumerable and List made from these. Using the object type results in values from mix of various types. When using a non-supported type, it results in a compile-time error.

Example:

[Test("My tests")]
public class MyTests : TestCategory {
    [Test("One is one")]
    public static void Equality() {
        Equal(1, 1);
    }
    
    [Test("Remote.GetOne")]
    public Task TestRemote() {
        Equal(await Remote.GetOne(), 1);
    }
 
    [Test("Equality is reflexive")]
    public Task TestRemote(int x) {
        Equal(x, x);
    }
}

TestGenerator

You must include a method like this in one of your classes:

    [Generated(typeof(TestGenerator))]
    public static WebSharper.IControlBody RunTests() => null;

This runs a compile-time generator, to create a JavaScript body for this method that runs all tests discovered in the current project.

It returns an IControlBody, which can be used inside the client (() => ...) helper to serve a page that runs the given tests, or call its ReplaceInDom method directly on the client, to replace a placeholder DOM element to the content generated by QUnit.

Example:

[JavaScript]
public class TestRunner {
    [Generated(typeof(TestGenerator))]
    public static IControlBody RunAllTests() => null;
}

Later, in server-side code, serve this on a sitelet endpoint:

    Content.Page(
        Title = "Unit tests",
        Body = client(TestRunner.RunAllTests)
    )

Assertion functions

See the F# API for available assertion functions. In C#, they are methods of the TestCategory class, which you can call from your test methods. Main differences are:

  • The method names are PascalCase instead of camelCase.
  • There are no ...Msg variants, instead there are overloaded versions of the same method that take an additional string argument for a test message.
  • There are no ...Async variants, instead you can use the await keyword in your test method that return a Task.
  • Note: the assertion checking for exceptions is using F# terminology so it is called Raises even for C#.

Examples

Expect

Using Expect to specify the number of tests, including Expect(0):

[Test("My Tests")]
public class MyTests : TestCategory {
    [Test]
    public void Equality() {
        Expect(2);
        Equal(1, 1);
        NotEqual(1, 2);
    }    
 
    [Test("Not throwing")]
    public void NotThrowing() {
        Expect(0);
        // a call to some outside method:
        OtherObject.DoNotFail();
        // if this throws an error, that still fails the test case, otherwise ok,
        // even though we are not registering any assertion
    }
}

IsTrue and IsFalse

Using IsTrue and IsFalse to check boolean expressions:

    [Test]
    public void Equality() {
        IsTrue(1 = 1);
        IsTrue(1 = 1, "One equals one");
    }
 
    [Test]
    public void FalseEquality() {
        IsFalse(1 = 2);
        IsFalse(1 = 2, "One equals two is false");
    }

Equal and JsEqual

Equal checks that correspond to the == operator in C#. While JsEqual checks that correspond to the == operator in JavaScript.

    [Test]
    public void Equality() {
        var a = MyObj(1);
        var b = MyObj(1);
        Equal(a, b); // ok only if MyObj overrides .Equals for structural equality
        Equal(a, b, "MyObj equality works");
    }
 
    [Test]
    public void JsEquality() {
        var a = MyObj(1);
        var b = MyObj(1);
        JsEqual(1, 1);
        JsEqual(1, 1, "One equals one");
        NotJsEqual(a, b); // JavaScript checks for reference equality
    }

Raises

Raises takes a lambda expression so that it can check if it throws an exception.

    [Test]
    public void Exceptions() {
        Raises(() => throw new Exception());
        Raises(() => throw new Exception(), "Exception is expected");
    }

Using randomly generated values

Random values can be used in your tests by using the RandomValues class, same as in F#.

    [Test]
    public void RandomTesting() {
        // enumerating on a custom generator
        foreach (var arr in RandomValues.ArrayOf(RandomValues.Int))
            Equal(arr, arr);
        // converting it to a Sample explicitly allows us to determine the size of the sample too
        foreach (var arr in new RandomValues.Sample<int[]>(RandomValues.ArrayOf(RandomValues.Int), 10))
            Equal(arr, arr);
        // we can also create a sample implicitly from a type
        foreach (var arr in new RandomValues.Sample<int[]>())
            Equal(arr, arr);
    }

On this page