Exercise 3: JavaScript-based Mock Data with Custom Logic

Now you'll implement JavaScript-based mock data that can generate dynamic data and handle custom actions. Here is the same approach as for the JSON files. You can name a javascript file with the name of the entity set like Books.js , Chapters.js , Reviews.js , Currencies.js , etc.

3.1 Implement JavaScript Mock Data

In this file you can implement several methods to control the behavior of the entity set.

  • Using getInitialDataSet you can generate the initial data set dynamically. This is an alternative approach to create mock data with more control over the data and the logic.
  • Using executeAction you can handle the custom actions. This is the entry point for all actions and functions.
  • You can use base API methods like fetchEntries , updateEntry , addEntry , etc. to manipulate the data.
API Documentation: You can look at the Mockserver API documentation for more information about the APIs. More info about base APIs .

1. Create Books.js with dynamic data generation:

Create webapp/localService/mainService/data/Books.js :

ex3/webapp/localService/mainService/data/Books.js View on GitHub ↗
module.exports = {
  // Generate initial dataset dynamically
  getInitialDataSet: function (contextId) {
    const books = [];
    const authors = ['Jane Austen', 'Mark Twain', 'Charles Dickens', 'Ernest Hemingway', 'Virginia Woolf'];
    const genres = ['Fiction', 'Mystery', 'Romance', 'Science Fiction', 'Biography'];

    for (let i = 0; i < 20; i++) {
      const randomAuthor = authors[Math.floor(Math.random() * authors.length)];
      const randomGenre = genres[Math.floor(Math.random() * genres.length)];

      books.push({
        ID: `550e8400-e29b-41d4-a716-44665544${String(i).padStart(4, '0')}`,
        title: `${randomGenre} Book ${i + 1}`,
        author: randomAuthor,
        price: Math.floor(Math.random() * 50) + 10,
        currency_code: Math.random() > 0.5 ? 'EUR' : 'USD',
        stock: Math.floor(Math.random() * 100),
        description: `A compelling ${randomGenre.toLowerCase()} story by ${randomAuthor}.`,
        coverUrl: `https://picsum.photos/300/400?random=${i}`,
        createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(),
        createdBy: 'admin',
        modifiedAt: new Date().toISOString(),
        modifiedBy: 'admin',
        IsActiveEntity: true,
        HasActiveEntity: false,
        HasDraftEntity: Math.random() > 0.8, // 20% chance of being in draft
      });
    }
    return books;
  },

  // Handle custom actions
  executeAction: async function (actionDefinition, actionData, keys) {
    console.log('Executing action:', actionDefinition.name);

    switch (actionDefinition.name) {
      case 'setDiscount':
        // Get current entry and apply discount based on parameters
        const currentEntries = await this.base.fetchEntries(keys);
        if (currentEntries.length > 0) {
          const currentBook = currentEntries[0];
          const discountPercentage = actionData.percentage || 10; // Default 10% if not provided
          const discountMultiplier = (100 - discountPercentage) / 100;
          const newPrice = Math.round(currentBook.price * discountMultiplier * 100) / 100;

          // Update the book with new price
          await this.base.updateEntry(keys, { price: newPrice });

          return {
            message: `${discountPercentage}% discount applied to "${currentBook.title}"`,
            newPrice: newPrice,
            originalPrice: currentBook.price,
            reason: actionData.reason || 'Mock discount applied',
          };
        }
        break;

      case 'promoteBook':
        const entry = await this.base.fetchEntries(keys); // expecting only one entry
        const firstEntry = entry[0];
        // Mark book as promoted
        await this.base.updateEntry(keys, {
          description: 'PROMOTED: ' + firstEntry.description,
          stock: firstEntry.stock + 10,
        });
        return {
          message: 'Book has been promoted successfully!',
          promoted: true,
        };

      default:
        this.throwError(`Action ${actionDefinition.name} not implemented`, 501);
    }
  },
};

2. Start the application:

npm run start-mock
# or from root folder
npm run start:ex3

The app shows data generated by the JavaScript file.

You can test the actions by clicking on the actions in the action bar.

3.2 Debugging

You can use the debugger to step through the code and see the data in the console:

  • IDE Debug Terminal : Create a JavaScript Debug Terminal in VS Code for debugging

Breakpoint locations:

  • getInitialDataSet - Called when application starts and loads initial data
  • executeAction - Called when user clicks action buttons (setDiscount, promoteBook, etc.)

Watching for changes:

You can watch for changes in the data and the JavaScript file by setting watch: true in the ui5-mock.yaml file. This will reload the application when the data or the JavaScript file is changed.

Result : JavaScript mock data enables dynamic data generation and custom action implementations.

3.3 Simulate Slow Responses (optional)

Add a small wait in action handlers to mimic slower backend responses when testing UI behavior (e.g. busy indicators):

// In webapp/localService/mainService/data/Books.js
module.exports = {
  // ...
  executeAction: async function (actionDefinition, actionData, keys) {
    // Simulate latency (e.g., 800ms)
    await new Promise((r) => setTimeout(r, 800))

    switch (actionDefinition.name) {
      case 'setDiscount':
        // existing logic
        break
      default:
        this.throwError(`Action ${actionDefinition.name} not implemented`, 501)
    }
  },
}

Remove the delay once you no longer need to simulate latency.

Next Steps

Exercise 4 will show how to configure multiple OData services for microservices-style applications with distinct metadata and datasets.