EUnit

EUnit is a unit testing framework designed specifically for the Elements languages. It is modeled much like existing unit testing frameworks (such as NUnit or XUnit on .NET or JUnit on Java), but makes it easy to share tests across platforms.

EUnit provides a common API for writing and running tests on all platforms supported by Elements. That means you can use it as your one stop library to write all your unit tests – those that are platform specific, and those that can be shared – to test the same shared code on all platforms.

Getting Started

The easiest way to get started with testing is to add a new "Test Application" project to your solution from the template that is provided for all platforms and languages, in Fire, Water and Visual Studio.

The test project contains all the infrastructure needed to start writing tests.

Commonly, your test application needs to access some of the same types that your main application uses. There are many ways to achieve this:

  1. If your main project already uses a Shared Project that contains the code you want to test, you can simply add that shared project to your test application.
  2. Similarly, if your main project is a class library (opposed to an executable or app), you can simply directly reference that library as a Project Reference. This also applies if your main project is a Java or Android app.
  3. Lastly, you can share code by simply adding the same relevant source files from your main project to the Test Application project – in Fire or Water you can do that simply by dragging them over.

Once that is done, your Test Application should be ready to build and run – but first you probably want to add some actual tests to it.

Creating Tests

Adding tests is easy: simply create a new Test class from the provided template (or create a new class and change its ancestor to be Test), and add one or more public parameterless methods to it:

uses
  RemObjects.Elements.EUnit;

type
  MyTests = public class(Test)
  public

    method FirstTest;
    begin
      Check.IsTrue(true);
    end;

  end;
using RemObjects.Elements.EUnit;

public class MyTests : Test
{
    public void FirstTest()
    {
        Check.IsTrue(true);
    }
}
import RemObjects.Elements.EUnit

public class MyTests : Test {
    public func FirstTest() {
        Check.IsTrue(true)
    }
}
import RemObjects.Elements.EUnit.*;

public class MyTests : Test {
    public void FirstTest() {
        Check.IsTrue(true);
    }
}

In this example, the test class contains a single test method (FirstTest) that performs a single check – to see whether true is true. Hopefully, that test should succeed.

You can add as many test classes as you like; usually it makes sense to create separate test classes for each separate type of related set of types that you want to test. Each test class can have as many test methods as you need; typically, you will add a separate test method (referred to as an individual "test") for each API (or sub-feature of an API) that needs testing.

Each test method can contain, of course, any custom code you need to exercise the code you want to test, along with Checks or Asserts to validate if the assumptions you want to test are met, or not.

EUnit provides two global objects to perform these validations, called Check and Assert; both objects expose the same API and let you test for various conditions – whether a value is true or false, whether a generated value matches the expected value, and so forth.

Each call to a method on Check validates the passed condition, and generates a test failure. Even if a check failed, execution of the test continues to run. This allows you to chain multiple related checks (e.g. performing different operations oin the same base data). Some may fail, some may (hopefully) succeed, but each of the conditions will be tested in turn.

Each call to a method on Assert also validates the passed condition, and generates a test failure. But in Contrast to Check, a failed Assert will immediately terminate execution of the current test method, skipping all further code (other test methods and tests will of course still run).

The idea is to use Check to validate individual, "unrelated" operations, that may succeed or fail independently, and to use Assert when a failure makes all subsequent test results irrelevant or undetermined (for example because they rely on the previous test succeeding).

method FirstTest;
begin
  var hw := "hello World";
  Check.AreEqual(hw.SubstringToFirstOccurrenceOf(" "), "hello");
  Check.AreEqual(hw.SubstringFromFirstOccurrenceOf(" "), "world"); // fails

  var h := hw.Substring(0, 5);
  Assert.AreEqual(h.Length, 5); // if this one fails, the rest is really irrelevant.
  Check.AreEqual(h[0], 'h');
  Check.AreEqual(h[1], 'e');
  Check.AreEqual(h, "hello");
end;
public void FirstTest()
{
    var hw = "hello World";
    Check.AreEqual(hw.SubstringToFirstOccurrenceOf(" "), "hello");
    Check.AreEqual(hw.SubstringFromFirstOccurrenceOf(" "), "world"); // fails

    var h = hw.Substring(0, 5);
    Assert.AreEqual(h.Length, 5); // if this one fails, the rest is really irrelevant.
    Check.AreEqual(h[0], 'h');
    Check.AreEqual(h[1], 'e');
    Check.AreEqual(h, "hello");
}
public func FirstTest() {
  let hw = "hello World"
  Check.AreEqual(hw.SubstringToFirstOccurrenceOf(" "), "hello")
  Check.AreEqual(hw.SubstringFromFirstOccurrenceOf(" "), "world") // fails

  let h := hw.Substring(0, 5)
  Assert.AreEqual(h.Length, 5) // if this one fails, the rest is really irrelevant.
  Check.AreEqual(h[0], 'h')
  Check.AreEqual(h[1], 'e')
  Check.AreEqual(h, "hello")
}
public void FirstTest() {
    var hw = "hello World";
    Check.AreEqual(hw.SubstringToFirstOccurrenceOf(" "), "hello");
    Check.AreEqual(hw.SubstringFromFirstOccurrenceOf(" "), "world"); // fails

    var h = hw.Substring(0, 5);
    Assert.AreEqual(h.Length, 5); // if this one fails, the rest is really irrelevant.
    Check.AreEqual(h[0], 'h');
    Check.AreEqual(h[1], 'e');
    Check.AreEqual(h, "hello");
}

Once your first test is written, you can run your Test Application as a normal app (in Visual Studio) or optionally using the special Testing Mode discussed below, in Fire or Water. Your test will run, and you will see successes and failures printed out.

Test Discovery

EUnit uses customizable discovery to detect the tests in your project. By default, your Test Application created from template will have standard code in its Entry Point that uses the platform-appropriate means to discover tests, using the Discovery.DiscoverTests() method provided by the library and then launch the default test runner and listener. It is rare that you will need to tweak this behavior.

var lTests := Discovery.DiscoverTests();
Runner.RunTests(lTests) withListener(Runner.DefaultListener);
var lTests = Discovery.DiscoverTests();
Runner.RunTests(lTests) withListener(Runner.DefaultListener);
let lTests = Discovery.DiscoverTests()
Runner.RunTests(lTests, withListener: Runner.DefaultListener)
var lTests = Discovery.DiscoverTests();
Runner.RunTests(lTests) withListener(Runner.DefaultListener);

Running Tests from the IDE

If you work in Fire or Water, you can make use of special debugger-integrated testing to debug your app in a special Testing Mode. This allows you to capture and see test failure inline in your code, automatically run a different test app than when running your main project in in a regular debug session, and to run multiple test projects in one go.

Check out the Testing with EUnit in Fire and Water topic for more details.

API Overview

While EUnit is made up of many types, the following are the three classes you will most often interact with:

  • Discovery — performs the platform-specific discovery of test classes in the current application.
  • Runner — runs a suite of tests.
  • Test — the base class for your own test classes that you will implement.

Two global objects expose APIs to trigger test failures by checking for certain conditions:

  • Assert — exposes APIs for fatal checks that will abort the current test method when they fail.
  • Check — exposes APIs for non-fatal checks that will flag a test failure but continue.

Finally, EUnit allows you to implement custom UI or test reporting within your test app, by implementing a custom listener:

  • IEventListener — the interface to implement if you want to provide custom progress UI or reporting while tests run.
  • TableViewTestListener — a custom listener that visually shows test results on iOS.

See Also