Last week, we read a value from a myAvatar Form and based on that value changed the message returned to the user. This method works well for most myAvatar forms, however, there are additional considerations to make with forms that contain one or more multiple-iteration tables. That’s what we will cover today.
Multiple-iteration tables are used to enter multiple rows of information on a single form. For example, while entering an Incident in Incident Management you can enter multiple clients or staff without requiring multiple fields. Just add a row and use the same fields to enter the next. This is why the row we worked with last week is labeled “CurrentRow.” On most forms, this is just the form content. On multiple iteration tables this holds the currently selected row for entry or editing. Other rows in the multiple iteration table are under another label, “OtherRows.”
The Specification
For today’s example, let’s assume that we have a form in myAvatar for pet information that works like an assessment. The first section of the form includes an assessment date and a draft/final field. In the second section, we have a multiple iteration tale for listing information about the pet. For our example the only field we will consider is the pet’s name. When there is a pet name of Mr. Pinchy then we will we will return the message, “Wow!” Silly, yes. Painfully lame…probably.
The Unit Tests
Like before, we are going to create our unit tests first. Let’s add a Unit Test file to our Unit Test project named, HasMrPincyCommandTests.cs. It will have the same three tests as our other commands (i.e., can it return all three optionobject versions?). Then we want the following tests:
- Does it respond with error code 3 and an error message when a pet is named “Mr. Pinchy”?
- Does it respond with error code 0 and no error message when no pets are named “Mr. Pinchy”, no pets are listed, or the field is not present?
Ok. Here is an abbreviated sample of potential tests.
As before our test should not be able to compile yet due to the missing command.
The Command
We will create our command to throw a not implemented exception and make sure the using in place for our tests to compile.
using NLog;
using RS.ScriptLinkDemo.Objects.Advanced;
using System;
namespace RS.ScriptLinkDemo.Soap.Commands
{
public class HasMrPinchyPetCommand : IRunScriptCommand
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly OptionObjectBase _optionObject;
public HasMrPinchyPetCommand(OptionObjectBase optionObject)
{
_optionObject = optionObject;
}
public IOptionObject2015 Execute()
{
logger.Debug("HasMrPinchyPetCommand executed.");
throw new NotImplementedException();
}
}
}
If everything is setup correctly so far, then our solution should be able to compile and run all tests. Let’s try. It should have compiled, run the tests, and all of our HasMrPinchyPetCommand tests should have failed.
Ok. Now to configure it to read the field values. We can setup our loop just like last week.
public IOptionObject2015 Execute()
{
logger.Debug("HasMrPinchyPetCommand executed.");
string returnMessage = null;
if (_optionObject.Forms != null)
{
foreach (var form in _optionObject.Forms)
{
if (form.CurrentRow != null && form.CurrentRow.Fields != null)
{
foreach (var field in form.CurrentRow.Fields)
{
switch (field.FieldNumber)
{
case "137.1":
if (field.FieldValue == "Mr. Pinchy")
returnMessage = "Wow!";
break;
}
}
}
}
}
if (!string.IsNullOrEmpty(returnMessage))
return _optionObject.ToReturnOptionObject(3, returnMessage);
return _optionObject.ToReturnOptionObject();
}
Update your Execute method to use the above code to check for “Mr. Pinchy” in the CurrentRow. Now run your tests. Did everything pass except for the tests when “Mr. Pinchy” is in the OtherRows? Good. Now let’s check the OtherRows.
public IOptionObject2015 Execute()
{
logger.Debug("HasMrPinchyPetCommand executed.");
string returnMessage = null;
if (_optionObject.Forms != null)
{
foreach (var form in _optionObject.Forms)
{
// Check CurrentRow
if (form.CurrentRow != null && form.CurrentRow.Fields != null)
{
foreach (var field in form.CurrentRow.Fields)
{
switch (field.FieldNumber)
{
case "137.1":
if (field.FieldValue == "Mr. Pinchy")
returnMessage = "Wow!";
break;
}
}
}
// Check OtherRows
if (form.MultipleIteration && form.OtherRows != null)
{
foreach (var row in form.OtherRows)
{
foreach (var field in row.Fields)
{
switch (field.FieldNumber)
{
case "137.1":
if (field.FieldValue == "Mr. Pinchy")
returnMessage = "Wow!";
break;
}
}
}
}
}
}
if (!string.IsNullOrEmpty(returnMessage))
return _optionObject.ToReturnOptionObject(3, returnMessage);
return _optionObject.ToReturnOptionObject();
}
In the above we have added an additional check to the same form to loop through the other rows and their fields checking for the value. There are many ways to improve upon (refactor) this code, but it is intentionally shown this way for clarity.
Now let’s run our tests. Did they all pass? Great!
Add Command to CommandSelector
Just like before let’s update our CommandSelector and its Unit Tests to enable this command for use.
[TestMethod]
public void GetCommand_HasMrPinchyPet_ReturnsHelloWorld()
{
// Arrange
OptionObject2015 optionObject2015 = new OptionObject2015();
string parameter = "HasMrPinchyPet";
HasMrPinchyPetCommand expected = new HasMrPinchyPetCommand(optionObject2015);
// Act
IRunScriptCommand actual = CommandSelector.GetCommand(optionObject2015, parameter);
// Assert
Assert.AreEqual(expected.GetType(), actual.GetType());
}
Now the code for the CommandSelector.
public static IRunScriptCommand GetCommand(IOptionObject2015 iOptionObject, string parameter)
{
OptionObjectBase optionObjectBase = (OptionObjectBase)iOptionObject;
switch (parameter)
{
case "HasMrPinchyPet":
logger.Debug("Creating a HasMrPinchyPetCommand.");
return new HasMrPinchyPetCommand(optionObjectBase);
case "HelloWorld":
logger.Debug("Creating a HelloWorld command.");
return new HelloWorld(optionObjectBase);
case "IsDraft":
logger.Debug("Creating an IsDraftCommand.");
return new IsDraftCommand(optionObjectBase);
default:
logger.Warn("No command found matching parameter '" + (parameter ?? "") + "'. Creating a DefaultCommand.");
return new DefaultCommand(optionObjectBase, parameter);
}
}
Did all of our tests pass?
Test in SoapUI
To finish up, let’s test this in SoapUI like before. I am going to clone my IsDraftCommand Request to help create our new request test.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:RunScript>
<!--Optional:-->
<tem:optionObject2015>
<!--Optional:-->
<tem:EntityID>?</tem:EntityID>
<tem:EpisodeNumber>0</tem:EpisodeNumber>
<!--Optional:-->
<tem:Facility>?</tem:Facility>
<!--Optional:-->
<tem:Forms>
<!--Zero or more repetitions:-->
<tem:FormObject>
<!--Optional:-->
<tem:CurrentRow>
<!--Optional:-->
<tem:Fields>
<!--Zero or more repetitions:-->
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>136.92</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue>5/7/2020</tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>136.93</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue>D</tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
</tem:Fields>
<!--Optional:-->
<tem:ParentRowId>?</tem:ParentRowId>
<!--Optional:-->
<tem:RowAction>?</tem:RowAction>
<!--Optional:-->
<tem:RowId>?</tem:RowId>
</tem:CurrentRow>
<!--Optional:-->
<tem:FormId>?</tem:FormId>
<tem:MultipleIteration>0</tem:MultipleIteration>
</tem:FormObject>
<!--Zero or more repetitions:-->
<tem:FormObject>
<!--Optional:-->
<tem:CurrentRow>
<!--Optional:-->
<tem:Fields>
<!--Zero or more repetitions:-->
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>137.09</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue></tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>137.1</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue>Mr. Ditties</tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
</tem:Fields>
<!--Optional:-->
<tem:ParentRowId>?</tem:ParentRowId>
<!--Optional:-->
<tem:RowAction>?</tem:RowAction>
<!--Optional:-->
<tem:RowId>?</tem:RowId>
</tem:CurrentRow>
<!--Optional:-->
<tem:FormId>?</tem:FormId>
<tem:MultipleIteration>1</tem:MultipleIteration>
<!--Optional:-->
<tem:OtherRows>
<!--Zero or more repetitions:-->
<tem:RowObject>
<!--Optional:-->
<tem:Fields>
<!--Zero or more repetitions:-->
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>137.09</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue></tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
<tem:FieldObject>
<!--Optional:-->
<tem:Enabled>?</tem:Enabled>
<!--Optional:-->
<tem:FieldNumber>137.1</tem:FieldNumber>
<!--Optional:-->
<tem:FieldValue>Mr. Pinchy</tem:FieldValue>
<!--Optional:-->
<tem:Lock>?</tem:Lock>
<!--Optional:-->
<tem:Required>?</tem:Required>
</tem:FieldObject>
</tem:Fields>
<!--Optional:-->
<tem:ParentRowId>?</tem:ParentRowId>
<!--Optional:-->
<tem:RowAction>?</tem:RowAction>
<!--Optional:-->
<tem:RowId>?</tem:RowId>
</tem:RowObject>
</tem:OtherRows>
</tem:FormObject>
</tem:Forms>
<!--Optional:-->
<tem:NamespaceName>?</tem:NamespaceName>
<!--Optional:-->
<tem:OptionId>?</tem:OptionId>
<!--Optional:-->
<tem:OptionStaffId>?</tem:OptionStaffId>
<!--Optional:-->
<tem:OptionUserId>?</tem:OptionUserId>
<!--Optional:-->
<tem:ParentNamespace>?</tem:ParentNamespace>
<!--Optional:-->
<tem:ServerName>?</tem:ServerName>
<!--Optional:-->
<tem:SystemCode>?</tem:SystemCode>
<!--Optional:-->
<tem:SessionToken>?</tem:SessionToken>
</tem:optionObject2015>
<!--Optional:-->
<tem:parameter>HasMrPinchyPet</tem:parameter>
</tem:RunScript>
</soapenv:Body>
</soapenv:Envelope>
The above XML request does not reflect what an actual OptionObject would look like coming from myAvatar, but it has a valid structure. This could be cleaned up by removing the comments, replacing the “?” with valid values, and adding any additional fields on the form for testing.
Did you get the message back as expected? Change the values of the fields. Does is respond in unexpected ways?
Closing Thoughts
You have now seen my ideal, basic workflow for updating my ScriptLink projects.
- Define the specification.
- Write the tests to the specification.
- Create the command to through not implemented exception.
- Add intended functionality validating with the unit tests along the way.
- Once all intended functionality is in place and tested with unit tests, run it locally and test with SoapUI.
Once I have completed this process, then I can deploy the code to the Test server for continued testing from the UAT environment. It takes a little longer up front, but it has saved me a lot of headache later due to undetected regressions. They still may happen, but far less frequently and I found it much easier to correct when discovered.
Next week, we will look at setting a field value with ScriptLink. I hope you have a great week and are finding these ScriptLink examples useful.
One reply on “Reading Values from Multiple-Iteration Tables with ScriptLink”
[…] correcting or appending a value. In my ScriptLink work, I have had each of these use cases. In the previous articles, we looked at how to read field values. Now, let’s set […]
LikeLike