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:
- Successful Transaction: When a deposit or withdrawal is successful, the balance is updated correctly.
- 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.
- 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
BankAccountclass should have abalanceproperty (number). - The
depositmethod should add the given amount to thebalance. - The
withdrawmethod should subtract the given amount from thebalanceif sufficient funds are available. If not, it should throw an error with the message "Insufficient funds". - The transaction should be simulated using a
try...catchblock. If an error occurs within thetryblock, thecatchblock should revert thebalanceto its original value. - You must use Jest for testing.
- The tests should cover both successful transactions and transaction rollbacks.
- The
mockDatabasefunction is provided and should not be modified.
Notes
- Consider using
beforeEachin your Jest tests to reset theBankAccount's balance to a known state before each test. - Use
expectassertions in Jest to verify the final balance and that the correct error is thrown. - The
mockDatabasefunction 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
BankAccountclass. - Think about how to properly mock the error throwing behavior within the
withdrawmethod 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);
});
});