NAV 2009 SP1 – Testing Codeunits

One of the new features in NAV 2009 SP1 is C/AL Testability.

Let try using the new feature to test a simple codeunit – let us call the codeunit doTest. The codeunit will only contain a simple function, that includes a confirm and will look like this:

 cside |  copy code |? 
PROCEDURE TestConfirm@1112800000(useConfirm@1112800000 : Boolean) out : Boolean;
BEGIN
    IF useConfirm THEN
        IF CONFIRM('Do you confirm?',TRUE) THEN
          EXIT(TRUE)
        ELSE
          ERROR('No confirmation!');
    EXIT(FALSE);
END;


Before we are able to perform tests on codeunits / functions – we have to enable the Testability. This is done in Tools -> Option.

NAV2009-Options

Here you must set the “Show C/AL Testability Properties” to Yes.

Now you are ready to build a codeunit, which must contain the functions to handle the test of the codeunit doTest. My simple test codeunit, does only contain one function. To test this function we will be needing the following functions:

  • Confirm_Yes
  • Confirm_No
  • TestConfirm_TrueYes
  • TestConfirm_TrueNo
  • TestConfirm_False

So let’s take a close look on how to build the “Handler Test Functions” codeunit.

First create a new codeunit. Let us call it “Handler Test Functions”. In the Properties for the Codeunit set SubType to Test.

Handler

We want to test the function TestConfirm in the doTest codeunit. Because the function contains a CONFIRM, we need to have functions that can handle the answer Yes and No. So let us build these two function.

In the Global Var add the function Confirm_Yes. Before closing the Global Var window, select the Properties for the function and set the FunctionType to ConfirmHandler.

confirm

Next set the Local Vars for the new function. This is to create the necessary signatur to the ConfirmHandler. The Signatur for the ConfirmHandler is:

 cside |  copy code |? 
Function name(Question : Text[1024]; VAR Reply : Boolean)

So what you do, is adding the 2 parameters – Question and Reply to the Locals. Now you are ready to build the Code which shall handle the Confirm. It could look something like this:

 cside |  copy code |? 
[ConfirmHandler]
PROCEDURE Confirm_Yes@1112800000(Question@1112800001 : Text[1024];VAR Reply@1112800002 : Boolean);
BEGIN
      IF Question <> 'Do you confirm?'  THEN
        ERROR('Unknown Confirm text: '+ Question);
      Reply := TRUE;
END;

Now do the same to create a function for Confirm_No – and you should end up with a function like this:

 cside |  copy code |? 
[ConfirmHandler]
PROCEDURE Confirm_No@1112800004(Question@1112800001 : Text[1024];VAR Reply@1112800002 : Boolean);
BEGIN
      IF Question <>  'Do you confirm?'  THEN
          ERROR('Unknown Confirm text: '+Question);
      Reply := FALSE;
END;

Next is to create functions that handles the different test scenarios.

Again go into the Global Vars and add the new function here. Let us start with the function TestConfirm_TrueYes. This function, should handle the Yes case.

In the Properties for the function you now have to setup the FunctionType and the HandlerFunctions.

TestConfirm

FunctionType is set to Test. When FunctionType is Test – the function will automatically be executed by the Test Runner codeunit. Next set HandlerFunctions to Confirm_Yes. Now the function knows, that if a Confirm Message occurs, then use the HandlerFunction Confirm_Yes.

All you now have to do is to call the TestConfirm function in the doTest codeunit. You should end up with a function like this:

 cside |  copy code |? 
[Test]
[HandlerFunctions(Confirm_Yes)]
PROCEDURE TestConfirm_TrueYes@1112800005();
VAR
      doTest@1112800000 : Codeunit 50001;
BEGIN
      IF NOT doTest.TestConfirm(TRUE) THEN
        ERROR('Error in test - True Yes');
END;

Repeat this for the functions TestConfirm_TrueNo and TestConfirm_False.

TestConfirm_TrueNo will look like this:

 cside |  copy code |? 
[Test]
[HandlerFunctions(Confirm_No)]
PROCEDURE TestConfirm_TrueNo@1112800001();
VAR
      doTest@1112800000 : Codeunit 50001;
BEGIN
      ASSERTERROR doTest.TestConfirm(TRUE);
      IF GETLASTERRORTEXT <> 'No confirmation!' THEN
        ERROR('Unexpected error: ' + GETLASTERRORTEXT);
END;

Please notice, that the HandlerFunction is Confirm_No. Furthermore notice that we in this function uses ASSERTERROR and GETLASTERRORTEXT. We are doing this, because we are now expecting to get an error back from the doTest.TestConfirm function.

TestConfirm_False will look like this:

 cside |  copy code |? 
[Test]
PROCEDURE TestConfirm_False@1112800003();
VAR
      doTest@1112800000 : Codeunit 50001;
BEGIN
      IF doTest.TestConfirm(FALSE) THEN
          ERROR('Wrong answer');
END;

Now we have all test functions in place and are ready to build a test run codeunit. Let us call the codeunit Test Runner.
In the properties set the SubType to TestRunner.

TestRunner-Prop

Now all you have to do – is adding the call of the function in OnRun section:

 cside |  copy code |? 
CODEUNIT.RUN(CODEUNIT::"Handler Test Functions");

When you now executes the Test Runner codeunit – it will automatically execute all functions in the “Handler Test Function” codeunit, that has the FunctionType Test as property.

If you like to log any events that occur, you can add two functions to your Test Runner codeunit. OnBeforeTestRun and OnAfterTestRun. These function will be executed before and after each test run – a test run in our case – is equal with a function call.

Example:

 cside |  copy code |? 
PROCEDURE OnBeforeTestRun@1112800000(CodeunitID@1112800000 : Integer;CodeunitName@1112800001 : Text[30];FunctionName@1112800002 : Text[30]) Ok : Boolean;
BEGIN
      Before := CURRENTDATETIME;
      EXIT(TRUE);
END;
PROCEDURE OnAfterTestRun@1112800001(CodeunitID@1112800000 : Integer;CodeunitName@1112800001 : Text[30];FunctionName@1112800002 : Text[30];Success@1112800003 : Boolean);
VAR
      log@1112800004 : Record 50000;
BEGIN
      log.INIT;
      log.UnitId := CodeunitID;
      log.Unit := CodeunitName;
      log.Func := FunctionName;
      log.Before := Before;
      log.After := CURRENTDATETIME;
      IF Success THEN
        log.Status := log.Status::Success
      ELSE BEGIN
        log.Status := log.Status::Failure;
        IF FunctionName <> '' THEN
          log.Message := GETLASTERRORTEXT;
      END;
      log.INSERT(TRUE);
END;

These functions, will save the test result to a table and you will get a log similar to this:

test-log

That’s all folks! Now you can test codeunits with the use of C/AL Testability :-)

You can leave a response, or trackback from your own site.

One Response to “NAV 2009 SP1 – Testing Codeunits”

  1. Shawana Loose says:

    You really make it seem so easy with your presentation but I find this matter to be actually something which I think I would never understand. It seems too complex and very broad for me. I am looking forward for your next post, I will try to get the hang of it!

Leave a Reply


4 − three =