Hone logo
Hone
Problems

Testing Transaction Rollbacks with Jest and TypeScript

Ensuring your code handles database transactions correctly, especially rollbacks in case of errors, is crucial for data integrity. This challenge focuses on implementing and testing transaction rollback functionality using Jest and TypeScript, simulating a database interaction where a rollback is necessary. You'll be writing tests to verify that changes made within a transaction are properly reverted when an error occurs.

Problem Description

You are tasked with creating a simple service that manages a "bank account" with a balance. This service has two methods: deposit(amount: number) and withdraw(amount: number). Both methods should operate within a simulated transaction. If any error occurs during the transaction (simulated by throwing an error in the withdraw method under certain conditions), the transaction should be rolled back, meaning the balance should revert to its original state before the transaction began.

Your goal is to write Jest tests that verify the following:

  1. Successful Transaction: When a deposit or withdrawal is successful, the balance is updated correctly.
  2. Transaction Rollback: When an error occurs during a withdrawal (specifically, if the withdrawal amount exceeds the current balance), the transaction is rolled back, and the balance remains unchanged.
  3. Error Handling: The error is correctly thrown when a rollback is triggered.

You will be provided with a basic BankAccount class and a mock database function. Your task is to complete the tests to ensure the transaction rollback mechanism works as expected.

Examples

Example 1:

Input:
BankAccount: { balance: 100 }
deposit(50)

Output:
BankAccount: { balance: 150 }
Explanation: A successful deposit increases the balance.

Example 2:

Input:
BankAccount: { balance: 100 }
withdraw(150) // Attempt to withdraw more than the balance

Output:
BankAccount: { balance: 100 } // Balance remains unchanged
Error: "Insufficient funds" is thrown.
Explanation: The withdrawal fails due to insufficient funds, triggering a rollback. The balance returns to its original value, and an error is thrown.

Example 3:

Input:
BankAccount: { balance: 50 }
withdraw(25)

Output:
BankAccount: { balance: 25 }
Explanation: A successful withdrawal decreases the balance.

Constraints

  • The BankAccount class should have a balance property (number).
  • The deposit method should add the given amount to the balance.
  • The withdraw method should subtract the given amount from the balance if sufficient funds are available. If not, it should throw an error with the message "Insufficient funds".
  • The transaction should be simulated using a try...catch block. If an error occurs within the try block, the catch block should revert the balance to its original value.
  • You must use Jest for testing.
  • The tests should cover both successful transactions and transaction rollbacks.
  • The mockDatabase function is provided and should not be modified.

Notes

  • Consider using beforeEach in your Jest tests to reset the BankAccount's balance to a known state before each test.
  • Use expect assertions in Jest to verify the final balance and that the correct error is thrown.
  • The mockDatabase function is a placeholder and doesn't actually interact with a database. It's used to simulate potential database errors.
  • Focus on testing the rollback logic within the BankAccount class.
  • Think about how to properly mock the error throwing behavior within the withdraw method to test the rollback scenario.
// BankAccount.ts
class BankAccount {
  balance: number;

  constructor(initialBalance: number = 0) {
    this.balance = initialBalance;
  }

  deposit(amount: number): void {
    this.balance += amount;
  }

  withdraw(amount: number): void {
    try {
      if (amount > this.balance) {
        throw new Error("Insufficient funds");
      }
      this.balance -= amount;
    } catch (error: any) {
      // Simulate transaction rollback
      console.error("Transaction rolled back:", error.message);
      // No need to explicitly revert balance here, as it's already done in the catch block.
    }
  }
}

// mockDatabase.ts (Provided - Do not modify)
const mockDatabase = {
  // This is just a placeholder.  No actual database interaction.
};

export { BankAccount, mockDatabase };
// BankAccount.test.ts (Your code goes here)
import { BankAccount, mockDatabase } from './BankAccount';

describe('BankAccount', () => {
  let account: BankAccount;

  beforeEach(() => {
    account = new BankAccount(100); // Reset balance before each test
  });

  it('should deposit funds correctly', () => {
    account.deposit(50);
    expect(account.balance).toBe(150);
  });

  it('should withdraw funds correctly', () => {
    account.withdraw(25);
    expect(account.balance).toBe(75);
  });

  it('should rollback transaction and throw error when withdrawing insufficient funds', () => {
    const initialBalance = account.balance;
    const withdrawAmount = 150;

    expect(() => account.withdraw(withdrawAmount)).toThrow("Insufficient funds");
    expect(account.balance).toBe(initialBalance);
  });
});
Loading editor...
typescript