ballerina/test : 0.8.0

Module Overview

This module facilitates developers to write automation tests for Ballerina code in a simple manner. It provides a number of capabilities such as configuring the setup and cleanup steps at different levels, ordering and grouping of tests, providing value-sets to tests, and independence from external functions and endpoints via mocking capabilities.

Annotations

A Ballerina testsuite can be implemented using a set of annotations. The available annotations enable executing instructions before and after the testsuite or a single test, organize a set of tests into a group, define data-driven tests, specify an order of execution, disable tests and mocking.

Test Config

The following example shows a simple testsuite.

1import ballerina/test;
2
3// Test function
4@test:Config {}
5function testFunction() {
6 test:assertTrue(true, msg = "Failed!");
7}

The before and after attributes can be used to set the execution order by specifying the functions to run before and/or after the test.

1import ballerina/io;
2import ballerina/test;
3
4function beforeFunc() {
5 io:println("I'm the before function!");
6}
7
8@test:Config {
9 before: beforeFunc,
10 after: afterFunc
11}
12function testFunction() {
13 io:println("I'm in test function!");
14 test:assertTrue(true, msg = "Failed!");
15}
16
17function afterFunc() {
18 io:println("I'm the after function!");
19}

The dependsOn attribute can also be used to set the test execution order by specifying the list of functions that the test function depends on.

1import ballerina/io;
2import ballerina/test;
3
4@test:Config {
5 dependsOn: [testFunction2] }
6function testFunction1() {
7 io:println("I'm in test function 1!");
8 test:assertTrue(true, msg = "Failed!");
9}
10
11@test:Config {}
12function testFunction2() {
13 io:println("I'm in test function 2!");
14 test:assertTrue(true, msg = "Failed!");
15}

The dataProvider attribute can be used to assign a function to act as a data provider for a test.

1@test:Config{
2 dataProvider: dataGen
3}
4function dataProviderTest (int value) returns error? {
5 test:assertEquals(value, 1, msg = "value is not correct");
6}
7
8function dataGen() returns (int[][]) {
9 return [[1]];
10}

Before and After Suite

The BeforeSuite annotation is used to execute a particular function before the test suite is executed. This can be used to setup the prerequisites before executing the test suite.

1@test:BeforeSuite
2function beforeSuit() {
3 // initialize or execute pre-requisites
4}
5

The AfterSuite annotation is used to execute a particular function after the test suite is executed. This is used to tear-down the prerequisites or execute post actions after executing the test suite.

1@test:AfterSuite
2function afterSuit() {
3 // tear-down
4}
5

Before and After Groups

The BeforeGroups and annotation can be used to execute the function before the first test function belonging the specified groups.

1import ballerina/io;
2import ballerina/test;
3
4@test:BeforeGroups { value:["g1"] }
5function beforeGroupsFunc() {
6 io:println("I'm the before groups function!");
7}
8
9@test:Config { groups: ["g1"]}
10function testFunction1() {
11 io:println("I'm in test function 1!");
12 test:assertTrue(true, msg = "Failed!");
13}

The AfterGroups and annotation can be used to execute the function after the last test function belonging the specified groups.

1import ballerina/io;
2import ballerina/test;
3
4@test:AfterGroups { value:["g1"] }
5function afterGroupsFunc() {
6 io:println("I'm the after groups function!");
7}
8
9@test:Config { groups: ["g1"]}
10function testFunction1() {
11 io:println("I'm in test function 1!");
12 test:assertTrue(true, msg = "Failed!");
13}

Assertions

This module provides a number of assertions in order to verify the expected behaviour of a piece of code. These assertions can be used to decide if the test is passing or failing based on the condition.

Following sample shows how to use assertions in Testerina.

1import ballerina/test;
2
3class Person {
4 public string name = "";
5 public int age = 0;
6 public Person? parent = ();
7 private string email = "default@abc.com";
8 string address = "No 20, Palm grove";
9}
10
11@test:Config{}
12function testAssertIntEquals() {
13 int answer = 0;
14 int a = 5;
15 int b = 3;
16 answer = intAdd(a, b);
17 test:assertEquals(answer, 8, msg = "int values not equal");
18}
19
20@test:Config {}
21function testAssertNotEqualsString() {
22 string s1 = "abc";
23 string s2 = "def";
24 test:assertNotEquals(s1, s2, msg = "string values are equal");
25}
26
27@test:Config {}
28function testAssertExactEqualsObject() {
29 Person p1 = new;
30 Person p2 = p1;
31 test:assertExactEquals(p1, p2, msg = "Objects are not exactly equal");
32}
33
34@test:Config {}
35function testAssertNotExactEqualsObject() {
36 Person p1 = new;
37 Person p2 = new ();
38 test:assertNotExactEquals(p1, p2, msg = "Objects are exactly equal");
39}
40
41@test:Config {}
42function testAssertTrue() {
43 boolean value = true;
44 test:assertTrue(value, msg = "AssertTrue failed");
45}
46
47@test:Config {}
48function testAssertFalse() {
49 boolean value = false;
50 test:assertFalse(value, msg = "AssertFalse failed");
51}
52
53@test:Config {}
54function testAssertFail() {
55 if (true) {
56 return;
57 }
58 test:assertFail(msg = "AssertFailed");
59}
60
61function intAdd(int a, int b) returns (int) {
62 return (a + b);
63}

Mocking

The test module provides capabilities to mock a function or an object for unit testing. The mocking features can be used to control the behavior of functions and objects by defining return values or replacing the entire object or function with a user-defined equivalent. This feature will help you to test the Ballerina code independently from other modules and external endpoints.

Function Mocking

Function mocking allows to control the behavior of a function in the module being tested or a function of an imported module.

The annotation @test:Mock {} is used to declare a MockFunction object, with details of the name of the function to be mocked, as well as the module name if an import function is being mocked. The module name value of the annotation is optional if the function being mocked is not an import function.

1import ballerina/io;
2import ballerina/test;
3
4@test:Mock {
5 moduleName: "ballerina/io",
6 functionName: "println"
7}
8test:MockFunction printlnMockFn = new();
9
10int tally = 0;
11public function mockPrint(any|error... val) {
12 tally = tally + 1;
13}
14
15@test:Config {}
16function testCall() {
17 test:when(printlnMockFn).call("mockPrint");
18
19 io:println("Testing 1");
20 io:println("Testing 2");
21 io:println("Testing 3");
22 test:assertEquals(tally, 3);
23}

Declaring the annotation with this object will create a default mock object in place of the original function. Subsequent to the declaration, the function call should be stubbed using the available mocking features. Different behaviors can be defined for different test cases if required.

Samples

main.bal

1public function intAdd(int a, int b) returns (int) {
2 return a + b;
3}

test.bal

The following example shows different ways of stubbing a function call.

1import ballerina/test;
2
3@test:Mock {
4 functionName: "intAdd"
5}
6test:MockFunction intAddMockFn = new();
7
8public function mockIntAdd(int x, int y) returns int {
9 return x - y;
10}
11
12@test:Config {}
13public function testMockCall() {
14 // stubbing to invoke the created mock function
15 test:when(intAddMockFn).call("mockIntAdd");
16 test:assertEquals(intAdd(10, 6), 4);
17
18 // stubbing to return the specified value
19 test:when(intAddMockFn).thenReturn(5);
20 test:assertEquals(intAdd(10, 4), 5);
21
22 // stubbing to return a value based on input
23 test:when(intAddMockFn).withArguments(20, 14).thenReturn(100);
24 test:assertEquals(intAdd(20, 14), 100);
25}

Object Mocking

Object mocking enables controlling the values of member variables and the behavior of the member functions of an object. This is vital when working with client objects as the remote functions can be stubbed to return a mock value without having to actually make calls to the remote endpoint.

Mocking of objects can be done in two ways.The available features provide the functionality to substitute the real object with a user-defined mock object or to stub the behavior of required functions.

  1. Creating a test double (providing an equivalent object in place of the real)
  2. Stubbing the member function or member variable (specifying the behavior of functions and values of variables)

Creating a test double is suitable when a single mock function/object can be used throughout all tests whereas stubbing is ideal when defining different behaviors for different test cases is required.

Creating a test double

A custom mock object can be defined in place of the real object which should contain the member variables and functions that need to be replaced. The custom object should be made structurally equivalent to the real object via the mocking features in the test module. A runtime exception will be thrown if any of the defined functions or variables is not equivalent to the counterpart of the original object.

main.bal

1import ballerina/http;
2
3http:Client clientEndpoint = check new ("http://petstore.com");
4
5function getPet(string petId) returns http:Response | error {
6 http:Response result = check clientEndpoint->get("/pets?id="+petId);
7 return result;
8}
9
10function deletePet(string petId) returns error? {
11 http:Response res = check clientEndpoint->delete("/pets/delete?id="+petId);
12}

test.bal

1import ballerina/test;
2import ballerina/http;
3
4// Mock object definition for http:Client object
5public client class MockHttpClient {
6 remote isolated function get(@untainted string path, map<string|string[]>? headers = (),
7 http:TargetType targetType = http:Response) returns http:Response | http:PayloadType | http:ClientError {
8 http:Response response = new;
9 response.statusCode = 500;
10 return response;
11 }
12}
13
14@test:Config {}
15function testTestDouble() returns error? {
16 //mock object that would act as the test double to the clientEndpoint
17 clientEndpoint = test:mock(http:Client, new MockHttpClient());
18 http:Response res = check getPet("D123");
19 test:assertEquals(res.statusCode, 500);
20}

Stubbing member functions and variables of an object

The member functions and variables are stubbed to return a specific value or to do nothing when invoked. Using the test module, a default mock object of the specified type. The default action of any member function/variable is to panic upon invocation/retrieval. After mock object creation, the required functions and variables of the default mock object should be stubbed to return a value or to do nothing when called.

Samples

The following example demonstrates how to stub a member function to return a specific value.

1
2@test:Config {}
3function testThenReturn() returns error? {
4 clientEndpoint = test:mock(http:Client);
5
6 http:Response mockResponse = new;
7 mockResponse.statusCode = 300;
8
9 // stubbing to return the specified value
10 test:prepare(clientEndpoint).when("get").thenReturn(mockResponse);
11
12 http:Response res = check getPet("D123");
13 test:assertEquals(res.statusCode, 300);
14}

The following example demonstrates how to stub a member function to return different values on subsequent calls.

1@test:Config {}
2function testThenReturnSequence() returns error? {
3 clientEndpoint = test:mock(http:Client);
4
5 http:Response mockResponse1 = new;
6 mockResponse1.statusCode = 400;
7
8 http:Response mockResponse2 = new;
9 mockResponse2.statusCode = 401;
10
11 // Stubbing to return different values for each function call.
12 test:prepare(clientEndpoint).when("get").thenReturnSequence(mockResponse1, mockResponse2);
13
14 http:Response res = check getPet("D123");
15 test:assertEquals(res.statusCode, 400);
16 res = check getPet("D123");
17 test:assertEquals(res.statusCode, 401);
18}

The following example demonstrates how to stub a member function to do nothing when called.

1@test:Config {}
2function testDoNothing() returns error? {
3 clientEndpoint = test:mock(http:Client);
4
5 // Stubbing to return different values for each function call.
6 test:prepare(clientEndpoint).when("delete").doNothing();
7
8 error? err = deletePet("D123");
9 test:assertEquals(err, ());
10}

The following example shows how to return a value when a member variable is accessed.

1@test:Config {}
2function testMemberVariable() {
3 clientEndpoint = test:mock(http:Client);
4 test:prepare(clientEndpoint).getMember("url").thenReturn("https://foo.com/");
5 test:assertEquals(clientEndpoint.url, "https://foo.com/");
6}

Functions

[11]

assertEquals

Asserts whether the given values are equal.

assertExactEquals

Asserts whether the given values are exactly equal.

assertFail

Assert failure is triggered based on your discretion.

assertFalse

Asserts whether the given condition is false.

assertNotEquals

Asserts whether the given values are not equal.

assertNotExactEquals

Asserts whether the given values are not exactly equal.

assertTrue

Asserts whether the given condition is true.

createBallerinaError

Creates an AssertError with the custom message and category.

mock

Creates a mock object of provided type description.

prepare

Prepares a provided default mock object for stubbing.

when

Objects and functions related to function mocking Allows a function to stub.

Classes

[5]

FunctionStub

Represents an object that allows stubbing function invocations.

MemberFunctionStub

Represents an object that allows stubbing member function invocations.

MemberVariableStub

Represents an object that allows stubbing member variables retrieved.

MockFunction

Represents a MockFunction object.

MockObject

Represents a Mock object in which stubs for member functions and variables need to be specified.

Records

[5]

AfterGroupsConfig
AfterSuiteConfig
BeforeGroupsConfig
MockConfig

Configuration of the function to be mocked.

TestConfig

Configuration set for test functions.

Constants

[6]

ANY

Represents the placeholder to be given for object or record type arguments.

FUNCTION_CALL_ERROR

Represents the reason for function mocking related errors.

FUNCTION_NOT_FOUND_ERROR

Represents the reason for the non-existing member function related errors.

FUNCTION_SIGNATURE_MISMATCH_ERROR

Represents the reason for the function signature related errors.

INVALID_MEMBER_FIELD_ERROR

Represents the reason for the object member field related errors.

INVALID_OBJECT_ERROR

Represents the reason for the mock object related errors.

Annotations

[8]

AfterEach

Identifies afterTest function.

AfterGroups

Identifies afterGroup function.

AfterSuite

Identifies afterSuite function.

BeforeEach

Identifies beforeTest function.

BeforeGroups

Identifies beforeGroup function.

BeforeSuite

Identifies beforeSuite function.

Config
Mock

Identifies the MockFunction object

Errors

[6]

Error

Represents mocking related errors.

FunctionCallError
FunctionNotFoundError
FunctionSignatureMismatchError
InvalidMemberFieldError
InvalidObjectError