Skip to main content

Use External Entities

đŸŽ¯context

An External Entity is considered a proxy of an entity that is managed by another microservice. Normally, the external entity contains only the most important data that is regularly needed in the context of the aggregate to which the external entity belongs. Because of these specifics, it is handled differently than a usual Entity.

Description​

This how-to will cover the basics of External Entities, how to design it in the Solution Designer, but especially how to implement the logic and how to test it in a Domain Service project.

â„šī¸note

Please note that the links to the workbench tools in this tutorial only apply to the IBM Education Environment we provide. If you are using a different environment, e.g. your own installation, you will need to navigate directly to the required tools.

Purpose of an External Entity​

An External Entity serves as reference to an entity that is managed in another service. The External Entity itself holds only the most crucial data, which is necessary for the core functionality of this service (e.g., identifier of the entity in another service, name).

Storing this small subset of information redundantly, the dependency on the other service can be minimized. Only in case further details of this entity are needed, the other service has to be called and the data has to be loaded.

Even if the external entity is linked to the corresponding entity in the other service, there is no automatism to update the external entity once the other entity has been changed. This is important to know because the external entity is part of an aggregate in its own service. If the external entity is updated, it relies on the aggregate's lifecycle and not on the entity in the other service.

Example​

To understand External Entities, we are going to use an example, a Customer management service. The Customer Service has several External Entities, nevertheless, in this how-to we are only referring to the External Entity Employee.

The service manages not only Customer information in the narrower sense, but also a responsible in the company for dealing with a certain customer.

Hence, the Customer aggregate in this service has an array of responsibles, which refers to the External Entity Employee. There will be a distinct service to manage employees, having the relevant functions and containing the necessary information. However, the Employee (= External Entity) in the customer service contains only the most crucial information (e.g.; employeeId, employeeName, email and orgUnit).

Since the External Entity is part of the Customer aggregate, it is subject to the whole aggregates lifecycle. For example, it may be dependent on the status of the Customer, when the External Entity Employee is allowed to get updated and when not. To retrieve the "full Employee data", the Customer service can load the Employee (with help of an Integration service). Similar happens when the existence of the Employee gets validated. Whether the validation succeeds or fails and what shall happen afterwards is completely in the responsibility of the customer service and dependent on business requirements. Same applies if, for example, the employee's name has been changed in the Employee service.

To make it more concrete: Let's say the Employee got changed and the Customer service received an event to be informed about it. The Customer service can now decide on its own whether to update the External Entity Employee or not. This may depend on the status of the customer, e.g. an update shall only take place if the customer is not already in the DEACTIVATED state.

Summary​

The purpose of an External Entity can be summarized as follows

  • Deputy for an Entity which is managed in another service.
  • Contains only the most important properties redundantly - which might be needed frequently.
  • Provides possibility to load the whole data from the other service or to validate the existence.
  • External Entity is part of an aggregate and therefore dependent on the lifecycle of the aggregate.

Creating an External Entity in the -solutionDesigner​

As the other Entities, an External Entity can be created in the Domain Namespace of a domain service project.

External Entity in Solution Designer

As you can see above, there are, compared to a (Root) Entity, additional sections for an External Entity. In the following we are describing the additional sections.

For more information, please refer to Product Documentation.

Constructor properties​

Basically, the constructor properties are meant to be provided as input when an instance of an External Entity shall be created. In this sense it is comparable to an input entity of a Domain Service or a Command.

â—ī¸info

You should know that not in every case all properties must be added as Constructor Properties. It is dependent on the concrete use case or business requirement. So, you can either provide all properties in case you know that the input data can be trusted and is up-to-date. On the other hand, it is also possible to provide only one property, e.g. the employeeId, to be able to identify the Employee in the other service and then get the rest of properties from the external service (using an Integration Service).

Known Entities​

An External Entity is supposed to be mapped to at least one Entity in the other service. Usually, the integration into the service happens in the Integration layer using a dedicated Integration Service, which is supposed to provide an Entity as output. In any Integration Namespace, an Entity can be added as Known Entity.

â„šī¸note
  • Only in the section Entities of an Integration namespace, modelled Entities can serve as Known Entities. It is not possible for Integration Services created to be modelled as private output entities.
  • You also need to know that the code generation for the Load method of an External Entity happens only when a Known Entity is added.

Implementing the logic for an External Entity​

In the following, we will show you how to implement the logic for an External Entity. Depending on the design in the Solution Designer, the corresponding code files will be generated within an "externals" folder within the respective aggregate:

  • An implementation file for each External Entity, e.g. Employee.ts, which contains three generated methods (Construct, Load, Validate), which can be executed for an External Entity instance
  • A test file, e.g. Employee.test.ts

In the following, we will cover each implementation method. Please see also the Product Documentation for further information.

Constructor method

The Constructor method is used to construct a local instance of the External Entity and to fill the associated properties with values for the construction defined properties. As code sample, you'll find below the Constructor method from the External Entity Employee in the Customer service:

Employee.ts Constructor method
export class Constructor extends entityConstructors.cust_EmployeeConstructor {
/**
* constructor method of external entity reference
*/
public async create(): Promise<void> {
const log = this.util.log;
log.debug('Constructor.execute()');

// Map input to Employee instance;
this.instance.employeeId = this.input.employeeId;
this.instance.employeeName = this.input.employeeName;
this.instance.email = this.input.email;
this.instance.orgUnit = this.input.orgUnit;
}
}
â„šī¸note

The constructor method above takes all values from the input. If an initial check is required to see if the employee exists, or if only the property required for identification (employeeId) is to be provided in the input, the matching integration service must first be called to collect the required values.

Load method

The Load method is meant to load the Entity from the external service. Usually this happens by calling an Integration Service in the matching Integration Namespace. It is intended that the Load returns undefined if the Integration Service has not found an Entity. In this case the Integration Service is supposed to throw a BusinessError, like EmployeeNotFound.

â„šī¸note
  • It is necessary to overwrite the generated code public async load(): Promise<void> by changing the Promise to the Entity you want to respond as output. For a sample see the code snippet below.
  • After calling the Integration Service, it is important to check whether the output exists. If this check does not occur, it is not possible to access the properties of the returned object. This is to prevent accessing properties that may not exist. This check is necessary for calling functions that are built to return two different types such as services that return either void or their output entity.

The code snippet below shows the implementation for the Load method of our example.

â—ī¸info

Looking at the code snippet, you will notice that the implemented Load method does not only return the values of the External Entity but the whole set of information the Integration Service provides. This is desired to provide all available information from the other service.

Employee.ts Load method
export class Load extends entityLoaders.cust_EmployeeLoader  {
/**
* load method of external entity reference - loads external entity from external system
*/
// public async load(): Promise<void> {
public async load(): Promise<emp_Employee> {
const log = this.util.log;
log.debug('Load.execute()');
// Creating input for GetEmployee integration service
const input = this.factory.entity.emp.GetEmployee_Input();
// Setting employeeId from Employee instance
input.employeeId = this.instance.employeeId;

try {
const serviceOutput = await this.services.emp.GetEmployee(input);
if(!serviceOutput) {
return;
}

// Create instance of emp:Employee and set values from integration service output
const employee: emp_Employee = this.factory.entity.emp.Employee();
employee.employeeId = serviceOutput.employeeId;
employee.email = serviceOutput.email;
employee.function = serviceOutput.function;
employee.orgUnit = serviceOutput.orgUnit;
employee.status = serviceOutput.status;
employee.validFrom = serviceOutput.validFrom;
employee.notes = serviceOutput.notes;
employee.validUntil = serviceOutput.validUntil;

// Create instance of emp:Person and set values
const person: emp_Person = this.factory.entity.emp.Person();
person.birthDay = serviceOutput.person.birthDay;
person.partyId = serviceOutput.person.partyId;
person.partyName = serviceOutput.person.partyName;
person.city = serviceOutput.person.city;
person.gender = serviceOutput.person.gender;

// Set person for Employee
employee.person = person;
// Return Employee
return employee;
}
catch (e) {
if (this.isInstanceOf.businessError.emp.EmployeeNotFound(e)) {
log.debug('Employee not found business error caught.');
// When employee wasn't found (404) no return object is expected from Load().
return undefined;
}
else {
// When something else went wrong thrown a GeneralError which can be interpreted differently by caller
log.debug('Loading Employee went wrong.');
const message = "Couldn't load Employee due to unknown reasons.";
const code = 'Employee not loaded.';
throw new GeneralError(message, code);
}
}
}
}

Validate method

The Validate method is intended to verify the existence of the Entity that matches the External Entity. Therefore, the Validate method calls the Load method, and depending on the output (either an output will be provided or not), the Validate returns either true or false. If an update of the External Entity shall be performed, the Validate must be called with the boolean update = true.

â„šī¸note

In case an error from the Load method was caught, also the Validate method should throw an error to be able to distinguish between the validation result false (Entity does not exist any more in the external service) or something else went wrong.

Employee.ts Validate method
export class Validate extends entityValidators.cust_EmployeeValidator {
/**
* validate method of external entity reference - checks if external entity still exists in external system
* @param update indicates whether local properties should be updated after validating entity on external system
* @returns boolean indicating whether external entity still exists in external system
*/
public async validate(update: boolean): Promise<boolean> {
const log = this.util.log;
log.debug('Validate.execute()');
try {
// Load Employee
const loadOutput: emp_Employee = await this.instance.load();
if (!loadOutput) {
return false; // Validation failed!
}
else {
if (update === true) {
// When update = true, update Employee with output from Load
// Note that employeeId should not change (therefore no update needed)
this.instance.employeeName = loadOutput.person.partyName;
this.instance.email = loadOutput.email;
this.instance.orgUnit = loadOutput.orgUnit;
}
return true; // Validation suceeded!
}
}
catch (e) {
if (this.isInstanceOf.error.GeneralError(e)) {
const code = 'ValidationNotPerformed';
const message = 'Validation could not be performed because of an error.';
const detailMessage = 'Validation of cust:Employee failed because of an unexpected error.';
throw new GeneralError(message, detailMessage, code);
}
}
}
}

Testing external entity implementation

As any other business relevant code, it is recommended to test your External Entity implementation. Below, you will find some examples how to test it.

â—ī¸info

Please consider that integration tests (using the generated test files) will be probably not enough for External Entities. The Reason is that it is not possible to mock the results from the Integration Service as well as the output of the Load method. So the tests would rely on having the external service running and the existence of the instance you would expect to exist for testing.

Integration tests

For the integration test of each External Entity, a test file (*.test.ts) will be generated. Below, an example for testing the Constructor method is shown.

â„šī¸note

For testing the Load and Validate methods, writing unit tests instead of integration tests is recommended to avoid to be dependent on an external service and the existence of a certain test instance.

Code for testing Constructor method
describe('create', () => {
it('works', async () => {
const runner = new externalEntityRunners.cust_EmployeeConstructorRunner();
// Factory Constructor Input Entity
runner.constructorInputEntity = testEnvironment.factory.entity.cust.Employee_ConstInput();
// Set values for constructor input
runner.constructorInputEntity.employeeId = 'E000000001';
runner.constructorInputEntity.employeeName = 'Max Employee';
runner.constructorInputEntity.email = 'max.employee@email.de';
runner.constructorInputEntity.orgUnit = 'Sales';
// Employee instance must be explicitily created
runner.entityInstance = await testEnvironment.factory.external.cust.Employee(runner.constructorInputEntity);
// Trigger test runner
await runner.run();
// Check results
expect(runner.output.employeeId).to.equal(runner.constructorInputEntity.employeeId);
expect(runner.output.employeeName).to.equal(runner.constructorInputEntity.employeeName);
expect(runner.output.email).to.equal(runner.constructorInputEntity.email);
expect(runner.output.orgUnit).to.equal(runner.constructorInputEntity.orgUnit);
});
});

Unit tests

For unit testing, no stub files are generated. Before starting to write unit tests for your implementation, please have a look into the how-to Define Unit Tests (Typescript). There, the general concept of unit testing your Domain Service projects is described.

Below, you will find the concrete unit test example divided into several steps and test cases.

Import dependencies

Import the required dependencies such as the entities, TestEnvironment, Context and the external entity methods, which need to be tested. Also import the sinon dependencies, the expect function, the ServiceTrigger, the GeneralError and the entity, which is used as output of the integration service.

Employee.unit.test.ts (1)
// Importing needed classes and interfaces from solution framework
import { Context, DebugRequestContext, externals, loadAndPrepareDebugConfig, TestEnvironment} from 'solution-framework';

// Importing needed chai and ts-sion
import { expect } from 'chai';
import sinon , {StubbedInstance, stubObject} from 'ts-sinon';

// Importing the implementation classes
import { Load as LoadBase } from './Employee';
import { Validate as ValidateBase } from './Employee';

// Importing service trigger class that we want to mock
import { ServiceTrigger } from 'solution-framework/dist/sdk/v1/solution/service/ServiceTrigger';

// Importing as output used entity and GeneralError
import { emp_Employee } from 'solution-framework/dist/sdk/v1/namespace/entity/emp_Employee';
import { GeneralError } from 'solution-framework/dist/sdk/v1/error/GeneralError';

Testing Load method - Declare variables and create test instance

For testing the Load method, it is necessary to mock the Integration Service GetEmployee. Additionally, a test instance of the External Entity must be created to be able to run the Load method on it.

Employee.unit.test.ts (2)
describe('cust:Employee_Load', () => {
// As service classes are protected, we need to extend it and use it in our tests
class Load extends LoadBase {
constructor(instance: externals.cust_Employee, requestContext: Context) {
super(instance, requestContext);
}
}
// Declare mock
let employeeIntegrationServiceOutput: emp_Employee;

// Declare service instance
let employeeLoadInstance: Load;

// Declare ext. entity Instance
let extEmployeeInstance: externals.cust_Employee;

// Declare serviceTriggerStubHelper stub for mocking ServiceTrigger
let serviceTriggerStubHelper: StubbedInstance<ServiceTrigger>;

// Create instance of test environment that provides access to factory to create new entity instances
const testEnvironment = new TestEnvironment();

before(async () => {
// Load needed request context
const requestContext = loadAndPrepareDebugConfig().requestContext;

// Create constructor input
const constructorInput = testEnvironment.factory.entity.cust.Employee_ConstInput();
constructorInput.employeeId = 'E00000001';
constructorInput.email = 'email@test.de';
constructorInput.employeeName = 'Mary Smith';
constructorInput.orgUnit = 'OrgUnit';

// Create external Employee instance
extEmployeeInstance = await testEnvironment.factory.external.cust.Employee(constructorInput);

// Create a service instance that we will test
// Notice that it needs the domain Json data and not the input object) => inputInstance['_getDomainJSON']()
employeeLoadInstance = new Load(extEmployeeInstance, requestContext);

// Mock the service trigger
serviceTriggerStubHelper = stubObject<ServiceTrigger>(employeeLoadInstance.services);

// Define expected output from integration service
employeeIntegrationServiceOutput = testEnvironment.factory.entity.emp.Employee();
const person = testEnvironment.factory.entity.emp.Person();
person.birthDay = new Date(1970,5,6);
person.city = 'Regensburg';
person.gender = 'FEMALE';
person.partyId = 'P000000001';
person.partyName= 'Mary Smith';
employeeIntegrationServiceOutput.person = person;
employeeIntegrationServiceOutput.employeeId = 'E00000001';
employeeIntegrationServiceOutput.status = 'ACTIVE';
employeeIntegrationServiceOutput.function = 'Function';
employeeIntegrationServiceOutput.orgUnit = 'OrgUnit';
employeeIntegrationServiceOutput.notes = 'Notes';
employeeIntegrationServiceOutput.validFrom = new Date();
});

after(async () => {
// Remove all instances that were created
await testEnvironment.cleanup();
sinon.restore();
});

//
// Below the test cases are described
//

});

Load - Successful Load

To test whether the Load method was executed successfully, the result of the Load must be compared to the Integration Service (GetEmployee) mocked output.

â—ī¸info

Please note that the following code must be placed within the 'describe block' as commented in the code snippet above. Same applies for the following test cases.

Employee.unit.test.ts (3)
it('Load works - Employee loaded', async () => {
// Create a mocked service function to replace the one in the serviceTriggerStubHelper
const mockedGetEmployeeService = sinon.fake(() => {
return employeeIntegrationServiceOutput;
});
// Replace the stub function with the mocked function
serviceTriggerStubHelper.emp.GetEmployee = mockedGetEmployeeService;

// Call load method
const resultLoad: emp_Employee = await employeeLoadInstance.load();

// Checking result of Load
sinon.assert.calledOnce(mockedGetEmployeeService);
expect (resultLoad.employeeId).to.equal(employeeIntegrationServiceOutput.employeeId);
expect (resultLoad.email).to.equal(employeeIntegrationServiceOutput.email);
expect (resultLoad.function).to.equal(employeeIntegrationServiceOutput.function);
expect (resultLoad.orgUnit).to.equal(employeeIntegrationServiceOutput.orgUnit);
expect (resultLoad.notes).to.equal(employeeIntegrationServiceOutput.notes);
expect (resultLoad.status).to.equal(employeeIntegrationServiceOutput.status);
expect (resultLoad.validFrom).to.equal(employeeIntegrationServiceOutput.validFrom);
expect (resultLoad.validUntil).to.equal(employeeIntegrationServiceOutput.validUntil);
expect (resultLoad.person.birthDay).to.equal(employeeIntegrationServiceOutput.person.birthDay);
expect (resultLoad.person.city).to.equal(employeeIntegrationServiceOutput.person.city);
expect (resultLoad.person.gender).to.equal(employeeIntegrationServiceOutput.person.gender);
expect (resultLoad.person.partyId).to.equal(employeeIntegrationServiceOutput.person.partyId);
expect (resultLoad.person.partyName).to.equal(employeeIntegrationServiceOutput.person.partyName);
});

Load - Employee not found

To test the case that the Integration service throws a "Not-Found-Business Error", you would need to mock this Business Error, which is unfortunately not possible. Nevertheless, to test this roughly you could do it as in the code snippet below described. To follow this approach you need to throw a GeneralError (instead of the Business Error) in the test case. Additionally, you need (only for running the test) to change the implementation to not check for the type of caught error (and then to return undefined).

đŸ”Ĩdanger

Please be aware that the described approach is only a crutch for testing the "Not found" case. It does not allow to test this scenario in its entirety. If you do it in this way, please make sure to make the adjustments in the implementation class undone after testing.

Employee.unit.test.ts (4)
 it('Load: employee not found - *** Needs manual adjustments to run this test case ***', async () => {
// *************************************** IMPORTANT NOTE FOR TEST EXECUTION ***************************************
// In this (test) case it is a necessary precondition that the GetEmployee integration service throws a BusinessError emp:EmployeeNotFound!
// Since it is not possible to throw such an error in the test, a GeneralError will be thrown in this test instead.
// To finish this test case successfully, an adjustment in the Employee.ts implementation file is necessary.
// The following check for the BusinessError needs to get disabled: "if (this.isInstanceOf.businessError.emp.EmployeeNotFound(e))" (in line 78),
// so that the in this case expected behaviour to return undefined can be tested. Please be aware to enable checking the type of error again,
// after running the unit test. Because without manipulating the implementation the test won't run successfully, this test case
// implementation is commented out by default.

/*
// Create a mocked service function to replace the one in the serviceTriggerStubHelper
const mockedGetEmployeeService = sinon.fake(() => {
throw new GeneralError('Employee not found', 'Employee with the provided employee id was not found.', 'emp:EmployeeNotFound');
});

// Replace the stub function with the mocked function
serviceTriggerStubHelper.emp.GetEmployee = mockedGetEmployeeService;

// Call load method
const resultLoad: emp_Employee = await employeeLoadInstance.load();

// Checking result of Load
sinon.assert.calledOnce(mockedGetEmployeeService);
expect (resultLoad).to.equal(undefined); // When the Load receives the BusinessError emp:EmployeeNotFound, it is expected to get no output (undefined)
*/
});

Testing Employee_Validate - Declare variables and create test instance

For testing the Validate method, it is necessary to mock the Load method. Additionally, a test instance of the External Entity must be created to be able to run the Validate method on it.

Employee.unit.test.ts (5)
describe('cust:Employee_Validate', () => {

let requestContext: DebugRequestContext;

// As service classes are protected, we need to extend it and use it in our tests
class Validate extends ValidateBase {
// eslint-disable-next-line no-shadow
constructor(instance: externals.cust_Employee, requestContext: Context) {
super(instance, requestContext);
}
}
// Declare mock
let loadedEmployee: emp_Employee;

// Declare service instance
let employeeValidateInstance: Validate;

// Declare ext. entity Instance
let extEmployeeInstance: externals.cust_Employee;

// Create instance of test environment that provides access to factory to create new entity instances
const testEnvironment = new TestEnvironment();

beforeEach(async () => {
// Load needed request context
requestContext = loadAndPrepareDebugConfig().requestContext;

// Create constructor input
const constructorInput = testEnvironment.factory.entity.cust.Employee_ConstInput();
constructorInput.employeeId = 'E00000001';
constructorInput.email = 'email@test.de';
constructorInput.employeeName = 'Mary Smith';
constructorInput.orgUnit = 'OrgUnit';

// Create external Employee instance
extEmployeeInstance = await testEnvironment.factory.external.cust.Employee(constructorInput);

// Define expected output from Load
loadedEmployee = testEnvironment.factory.entity.emp.Employee();
loadedEmployee.person = testEnvironment.factory.entity.emp.Person();
loadedEmployee.email = 'email_updated@test.de';
loadedEmployee.person.birthDay = new Date(1970,5,6);
loadedEmployee.person.city = 'Regensburg';
loadedEmployee.person.gender = 'FEMALE';
loadedEmployee.person.partyId = 'P000000001';
loadedEmployee.person.partyName= 'Mary Smith-Forest';
loadedEmployee.employeeId = 'E00000001';
loadedEmployee.status = 'ACTIVE';
loadedEmployee.function = 'Function';
loadedEmployee.orgUnit = 'OrgUnit2';
loadedEmployee.notes = 'Notes';
loadedEmployee.validFrom = new Date();
});

after(async () => {
// Recommended: remove all instances that were created
await testEnvironment.cleanup();
sinon.restore();
});

//
// Below the test cases are described
//

});

Validate - Validation result = true and update peformed

Depending on the parameter update, the Validate method shall update the External Entity or not. This test case checks the validation and whether the update was performed as expected. Therefore, it will be tested if the External Entity was updated with the values from the Load.

Employee.unit.test.ts (6)
it('Validate works - update performed', async () => {

// Create a service instance that we will test
// Notice that it needs the domain Json data and not the input object) => inputInstance['_getDomainJSON']()
employeeValidateInstance = new Validate(extEmployeeInstance, requestContext);
const mockedEmployeeLoad = sinon.fake(()=> {
return loadedEmployee;
});

employeeValidateInstance.instance.load = mockedEmployeeLoad;

// Call validate method
const validateResult: boolean = await employeeValidateInstance.validate(true);
// Checking result
sinon.assert.calledOnce(mockedEmployeeLoad);
expect(validateResult).to.equal(true);
expect(extEmployeeInstance.email).to.equal(loadedEmployee.email);
expect(extEmployeeInstance.employeeId).to.equal(loadedEmployee.employeeId);
expect(extEmployeeInstance.employeeName).to.equal(loadedEmployee.person.partyName);
expect(extEmployeeInstance.orgUnit).to.equal(loadedEmployee.orgUnit);
});

Validate - Validation result = true but no update desired

This test case checks the validation. To ensure that the External Entity is not modified, the test verifies whether the instance has not been modified.

Employee.unit.test.ts (7)
  it('Validate works - no update performed', async () => {

// Create a service instance that we will test
// Notice that it needs the domain Json data and not the input object) => inputInstance['_getDomainJSON']()
employeeValidateInstance = new Validate(extEmployeeInstance, requestContext);
const mockedEmployeeLoad = sinon.fake(()=> {
return loadedEmployee;
});

employeeValidateInstance.instance.load = mockedEmployeeLoad;

// Call validate method
const validateResult: boolean = await employeeValidateInstance.validate(false);

// Checking result
sinon.assert.calledOnce(mockedEmployeeLoad);
expect(validateResult).to.equal(true);
expect(extEmployeeInstance.email).to.not.equal(loadedEmployee.email);
expect(extEmployeeInstance.employeeId).to.equal(loadedEmployee.employeeId);
expect(extEmployeeInstance.employeeName).not.to.equal(loadedEmployee.person.partyName);
expect(extEmployeeInstance.orgUnit).to.not.equal(loadedEmployee.orgUnit);
});

Validate - Validation result = false

Again, the Validation method shall be checked, but in this case it is expected that the validation result is false. To prove this, the result of the Load got mocked to return undefined.

Employee.unit.test.ts (8)
  it('Validate - validation worked but failed because Employee could not get loaded', async () => {

// Create a service instance that we will test
// Notice that it needs the domain Json data and not the input object) => inputInstance['_getDomainJSON']()
employeeValidateInstance = new Validate(extEmployeeInstance, requestContext);
const mockedEmployeeLoad = sinon.fake(()=> {
return undefined;
});

employeeValidateInstance.instance.load = mockedEmployeeLoad;

// Call validate method
const validateResult: boolean = await employeeValidateInstance.validate(false);

// Checking result
sinon.assert.calledOnce(mockedEmployeeLoad);
expect(validateResult).to.equal(false);
});

Validate - Validation not performed with error

Finally, it is checked whether an error (not of the type Business Error "Not found") hinders validation. Therefore, the Load is mocked to throw a General Error.

Employee.unit.test.ts (9)
  it('Validate was not performed - error occured', async () => {

// Create a service instance that we will test
// Notice that it needs the domain Json data and not the input object) => inputInstance['_getDomainJSON']()
employeeValidateInstance = new Validate(extEmployeeInstance, requestContext);
const message = "Couldn't load Employee due to unknown reasons.";
const code = 'Employee not loaded.';

const mockedEmployeeLoad = sinon.fake.throws(new GeneralError(code, message));

employeeValidateInstance.instance.load = mockedEmployeeLoad;
try {
// Call validate method
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const validateResult: boolean = await employeeValidateInstance.validate(false);
}
catch (e) {
// Checking result
sinon.assert.calledOnce(mockedEmployeeLoad);
expect(e.code).to.equal('ValidationNotPerformed');
expect(e.message).to.equal('Validation could not be performed because of an error.');
expect(e.detailMessage).to.equal('Validation of cust:Employee failed because of an unexpected error.');
}
});

Conclusion​

🌟result

In this how-to you got an overview of the purpose of External Entities and how to model them in the Solution Designer. Furthermore, you were provided concrete examples of how to implement the logic for an External Entity and how to test it.