---
# System prepended metadata

title: 'Jam 07 - Exercise 1: Interface Implementation & Testing'
tags: [polymorphism, "2 \U0001F4DD in writing", inheritance, uml, interfaces, junit, abstract-classes, "3 \U0001F9EA in testing", "4 \U0001F973 done"]

---

---
title: "Jam 07 - Exercise 1: Interface Implementation & Testing"
tags: 
  - 2 📝 in writing
  - 3 🧪 in testing
  - 4 🥳 done
  - inheritance
  - interfaces
  - polymorphism
  - uml
  - abstract-classes
  - junit
---
<!-- markdownlint-disable line-length single-h1 no-inline-html -->
<!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } -->

{%hackmd dJZ5TulxSDKme-3fSY4Lbw %}

# Getting Started

Before diving into the exercises, let's set up our workspace:

1. Make sure you have committed all previous work
2. Create and switch to a new feature branch called `jam07`
3. Set up your source directory at `src/main/java/jam07`
4. Set up your test directory at `src/test/java/jam07`

:::info
🔧 **Environment Setup Tips**

- Verify your working directory is clean before creating the branch
- Check your current branch in IntelliJ's bottom-right corner
- Remember to create both main and test directories
- Review Jam 01's documentation if you need git command help

:::

Now that we have our workspace ready, let's begin exploring inheritance, interfaces, and JUnit testing!

# A Note About UML Diagrams

In previous jams, you created and maintained UML class diagrams showing relationships between your classes. For this jam, you are **not required** to maintain or update those class diagrams. This allows you to focus on learning new concepts like inheritance, interfaces, and JUnit testing.

:::info
🎨 **Friday Challenge**

If you'd like to challenge yourself and earn extra credit, you can:

1. Maintain your UML class diagram from previous jams
2. Add the new classes and relationships from Jam07
3. Submit the updated diagram as `Jam07-class-uml.drawio.png`

This extra credit opportunity can enhance your grade for this jam, but not completing it won't negatively impact your base grade. The extra credit points will be added on top of your earned points for the required work.
:::

However, this jam will introduce you to a new type of UML diagram: **State Diagrams**. These diagrams help visualize how objects change state over time and what triggers those changes. You **will** be required to create a state diagram in Exercise 2 to model the behavior of the Register.

:::success
🔑 **UML Focus for Jam07**

Required:

- Create UML state diagram for Register behavior (Exercise 2)
- Document state transitions and guards
- Show composite states

Optional (Extra Credit):

- Maintain and update class diagram
- Add new classes and relationships
- Document inheritance hierarchy

:::

# Exercise 1 - Interface Implementation & Testing

## Overview - Exercise 1

In this exercise, you'll create your first interface and implement it in multiple classes. Building on your experience with provided tests in Jams 05 and 06, you'll see how interfaces allow us to define common behavior across different types of objects.

:::info
🔑 **Key Concepts**

- Interface design and implementation
- Polymorphic behavior
- Search functionality design
- Test-driven development
- Code documentation

:::

## The Problem - Exercise 1

Our retail system needs a flexible way to search for different types of objects (products, transactions, employees). While each object type has different attributes, they all need to be searchable. This is a perfect use case for an interface!

**Requirements**:

1. Create a `Searchable` interface that defines a contract for search functionality
2. Implement the interface in both Product and Transaction classes
3. Add search functionality to ProductCatalog using the interface
4. Add generic search functionality to Register for searching transactions
5. Write test cases to validate both implementations
6. Document the interface and implementation decisions

This scenario demonstrates why interfaces are valuable - they allow us to define common behavior across different types of objects, even when those objects need to implement the behavior in different ways.

## Understanding Interfaces

An interface defines a contract that classes can implement. Think of it like a promise - any class that implements an interface promises to provide all the behavior defined by that interface.

For example, our `Searchable` interface will define what it means to be "searchable":

- Products will be searchable by name or SKU
- Transactions will be searchable by timestamp, amount, or state
- (Later, Registers will be searchable by name or ID)

Even though each class implements search differently, they all share the common contract defined by the interface.

## Understanding Polymorphism and Generics

Now that we understand what we want to build, let's think about how to implement it. We want to create a reusable way to search any list of searchable items. But how do we write one method that could work with both Products AND Transactions? 🤔

Your first instinct might be to use regular polymorphism:

```java
// First attempt - using polymorphism
private List<Searchable> searchList(List<Searchable> items, String query) {
    List<Searchable> results = new ArrayList<>();
    for (Searchable item : items) {
        if (item.matches(query)) {
            results.add(item);
        }
    }
    return results;
}
```

This seems reasonable! After all, both Product and Transaction implement Searchable, so this should work... right?

The problem appears when we try to use the results:

```java
List<Searchable> results = searchList(completedTransactions, "query");
Transaction firstMatch = results.get(0);  // Error! Can't convert Searchable to Transaction
```

We've lost the specific type information! Even though we know these are actually Transaction objects, Java doesn't - it only sees them as generic Searchable objects. We could try to convert them back to Transactions using casting, but that's error-prone - what if we accidentally tried to cast a Product to a Transaction? Our code would crash!

So... how do we make something that could safely return `Product`s OR `Transaction`s without risking these kinds of errors? 🤔

Let's look at how we've used `ArrayList` before. We've used it like:

```java
ArrayList<String> names = new ArrayList<>();  // holds Strings
ArrayList<Product> products = new ArrayList<>();  // holds Products
```

We've been using those angle brackets `<...>` to tell ArrayList what kind of thing it should hold.

Have you ever wondered how ArrayList can work with any type we give it? What if we could do the same thing with our search method? We saw that our first thought might be "this method returns Searchable", but that led to casting problems. Instead, what if we could make the return type itself be a variable - something we could set to Product or Transaction or whatever type we need?

Enter Generics! 🎉

When you look at the Java API documentation for ArrayList, you'll see it's defined as `ArrayList<T>`. That `<T>` is called a "type parameter" - it's a placeholder that gets replaced with whatever type we want to use. When we write `ArrayList<String>`, we're telling Java "replace T with String everywhere in ArrayList".

We can use this same idea for our method. Instead of using raw Searchable objects, we can tell Java "this method works with any type that is Searchable, but keep track of exactly which type it is":

![screenshot of method signature of searchList](https://www.dropbox.com/scl/fi/kqlxok72rv2d6wl3mzb7s/searchListBreakdown.png?rlkey=u0pm0isy4j2cynnajeh1kdvbl&raw=1)

Let's break this down:

- (1) Parameter: `List<T> items` means "this method takes a list and whatever type that list is, T is set to that Type"
- (2) Type Parameter Declaration: `<T extends Searchable>` means "T is restricted to any type that can be referred to as Searchable"
- (3) Return Type: `List<T>` means "this method returns a list of whatever T is"

This would result in:

```java
// Second attempt - using generics
default <T extends Searchable> List<T> searchList(List<T> items, String query) {
    List<T> results = new ArrayList<>();
    for (T item : items) {
        if (item.matches(query)) {
            results.add(item);
        }
    }
    return results;
}
```

This way, when we search transactions, we get back a `List<Transaction>`, and when we search products, we get back a `List<Product>`. The compiler maintains type safety while still allowing us to write one reusable method!

## Step 1: Create the Searchable Interface

Design and implement a `Searchable` interface according to this UML (`searchList` implementation can be found above):

```plantuml
@startuml
+interface Searchable<T> {
    +matches(query: String): boolean
    +searchList(items: List<T>, query: String): List<T>
}
@enduml
```

Create this `public` interface in a new file called `Searchable.java` in your `jam07` package. Including the package statement, any imports, and curly brackets, you should have 4 lines of actual code (not counting any JavaDocs).

:::info
🔍 **Default Methods in Interfaces**

Java 8 introduced default methods in interfaces, which allow us to provide a default implementation for interface methods. This is particularly useful for utility methods that would be implemented the same way by all implementing classes. In our case, the `searchList` method is a perfect candidate for a default method since it works the same way regardless of the implementing class.

:::

## Step 2: Implement in Product Class

Modify your existing `jam05.Product` class to implement the `jam07.Searchable` interface. You'll need to add the interface to the class declaration and implement the matches method. The matches method should check both the sku and the name of the product.

Consider these implementation decisions (feel free to start a discussion on Discord (or contribute to the existing one if someone already started it)):

- What should happen with null or empty queries?
- Should matches be case-sensitive?
- Should partial matches be allowed?

## Step 3: Implement in Transaction Class

Modify your existing `jam06.Transaction` class to implement the `jam07.Searchable` interface. You'll need to add the interface to the class declaration and implement the matches method. The matches method should check:

- Current state *(what method could you use to convert an enum to a string?)*
- Timestamp *(what method would convert this to a searchable string?)*
- Total amount *(what method would convert the total into a String currency representation? Did we already create a utility for this?)*

Consider the same implementation decisions as with `Product`:

- Null/empty query handling
- Case sensitivity
- Partial matches

## Step 4: Implement in Register Class

Modify your existing `jam06.Register` class to implement the `jam07.Searchable` interface. You'll need to add the interface to the class declaration and implement the matches method. The matches method should check:

- Register name *(what method would convert this to a lowercase searchable string?)*
- Drawer balance *(what method would convert this number into a searchable string?)*

Consider the same implementation decisions as with `Product`:

- Null/empty query handling
- Case sensitivity
- Partial matches

## Step 5: Add Searchable to ProductCatalog

You might be wondering why we need to make `ProductCatalog` implement the `Searchable` interface when we don't actually want to search for catalogs. The reason is that we want to use the interface's default `searchList` method to search through our products.

Think of it like borrowing a tool: to use the `searchList` tool that we created in the `Searchable` interface, our class needs to "sign the contract" (implement the interface) to get access to it. Even though we won't use all parts of the contract (the `matches` method), the benefit of getting access to `searchList` makes it worthwhile.

Modify your existing `jam05.ProductCatalog` class to implement the `jam07.Searchable` interface. Then add a search method that uses the interface's default `searchList` method. This method should search through the catalog's products using the `matches` method from the `Searchable` interface.

```java
public class ProductCatalog implements Searchable<Product> {
    // ... existing code ...

    @Override
    public boolean matches(String query) {
        // ProductCatalog itself isn't searchable, but we need to implement this
        // method since we're implementing the interface
        throw new UnsupportedOperationException("ProductCatalog does not support direct matching");
    }

    public List<Product> searchProducts(String query) {
        // Use the default searchList method from the Searchable interface
        return searchList(products, query);
    }
}
```

:::info
🔍 **Implementation Notes**

- The class must implement `Searchable<Product>` to use the default `searchList` method
- We implement `matches()` but throw an exception since ProductCatalog itself isn't searchable
- The `searchProducts` method uses the inherited default `searchList` method
- The search will work with both exact and partial matches based on Product's implementation
- Case sensitivity matches your implementation in Product
- This is a simpler, more straightforward implementation than writing our own search logic

:::

## Step 6: Add Transaction Search Method

Add a method to search transactions in the Register class according to this UML:

```plantuml
@startuml
class Register {
    +searchTransactions(query: String): List<Transaction>
}
@enduml
```

:::info
🔍 **Implementation Notes**

- Use the default searchList method from the Searchable interface (which we get for free as we've already implemented `Searchable` so we can search registers!)
- This method should search both completed and current transactions
    - Remember that searchList takes a single list as input
    - You'll need to figure out how to combine both transaction sources into one list (But don't modify your list of completed transactions!!!)
        - Consider what happens if currentTransaction is null
- The method should return a List<Transaction> for type safety
- You will test this method in the next step

:::

## Understanding JUnit

JUnit is a testing framework that helps us verify our code works correctly. You've seen JUnit tests in previous jams, but now it's time to write your own! Let's break down the key components:

### Test Class Structure

Unlike regular Java classes, test classes are exempt from the Javadoc requirement. However, this exemption comes with a responsibility: test code must be self-documenting. This means:

- Test method names must clearly describe what they are testing
- Test data setup should be obvious from the variable names
- Comments should only explain complex test scenarios or edge cases
- The Arrange-Act-Assert pattern should be clearly visible in the code structure

The Arrange-Act-Assert pattern is a standard way to structure test methods:

1. **Arrange**: Set up your test data and conditions
2. **Act**: Call the method you're testing
3. **Assert**: Verify the results

This pattern makes tests easier to read and maintain because it clearly separates what you're testing from how you're testing it. We'll see this pattern in action in the example below.

Here is an example of the structure of a test file without JavaDocs.  

```java
package jam0X;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class MyClassTest { //Usually named for the class you are testing
    // Fields for test data
    private MyClass myObject;
    private List<String> testData;

    // Runs before each test
    @BeforeEach
    void setUp() {
        myObject = new MyClass();
        testData = new ArrayList<>();
    }

    // Runs after each test
    @AfterEach
    void tearDown() {
        // Clean up test data - Not always necessary
        myObject = null;
        testData.clear();
    }

    // Individual test methods
    @Test
    void testSomething() {
        // Arrange: Set up test data
        // Act: Call the method being tested
        // Assert: Verify the result is what you expect
    }
}
```

### Key Concepts

1. **Test Methods**
   - Must be annotated with `@Test`
   - Follow the Arrange-Act-Assert pattern
   - Use descriptive names that explain what's being tested
   - Should test one specific behavior or a tightly coupled set of related behaviors
        - Note: the tests you've seen so far lean more towards the tighly coupled approach.  However, you may find it more useful to write for the specific behaviour approach for the advantages listed below

   :::info
   💡 **Testing One vs. Many Behaviors**

   There are two approaches to structuring test methods:

   1. **Single Behavior Tests**

      ```java
      @Test
      void shouldStartEmpty() {
          assertTrue(catalog.isEmpty());
          assertEquals(0, catalog.getProductCount());
      }

      @Test
      void testWithOneProduct() {
          catalog.addProduct(testProduct1);
          assertEquals(1, catalog.getProductCount());
      }

      @Test
      void testWithTwoProducts() {
          catalog.addProduct(testProduct1);
          catalog.addProduct(testProduct2);
          assertEquals(2, catalog.getProductCount());
      }
      ```

      - Tests one specific behavior
      - Makes failures easy to diagnose
      - Best for simple behaviors

   2. **Related Behaviors Tests**

      ```java
      @Test
      void testCountAndEmpty() {
          // Test empty catalog
          assertTrue(catalog.isEmpty());
          assertEquals(0, catalog.getProductCount());
          
          // Test with one product
          catalog.addProduct(testProduct1);
          assertFalse(catalog.isEmpty());
          assertEquals(1, catalog.getProductCount());
          
          // Test with multiple products
          catalog.addProduct(testProduct2);
          assertFalse(catalog.isEmpty());
          assertEquals(2, catalog.getProductCount());
      }
      ```

      - Tests multiple related behaviors
      - Useful when behaviors are tightly coupled
      - Can be more efficient for testing state transitions
      - Best when the behaviors are part of the same concept

   Choose based on:
   - How related the behaviors are
   - How complex each behavior is
   - How important it is to isolate failures
   - Whether the behaviors are part of the same concept
   :::

2. **Setup & Teardown**
    - `@BeforeEach` runs before each test
        - Use it to create fresh test objects
        - Avoid sharing state between tests
    - `@AfterEach` runs after each test
        - Use it to clean up resources
        - Close files, connections, etc.
        - This is not always necessary

3. **Assertions**

   ```java
   // Common assertions, there are many others!
   assertEquals(expected, actual);        // Values should be equal
   assertTrue(condition);                 // Condition should be true
   assertFalse(condition);                // Condition should be false
   assertNull(object);                    // Object should be null
   assertNotNull(object);                 // Object should not be null
   assertThrows(Exception.class, () -> {  // Code should throw exception
       // code that should throw
   });
   ```

4. **Test Organization**
   - Group related tests together
   - Use descriptive method names
   - Follow a consistent pattern
   - Test both normal and edge cases

### Example: Testing the Searchable Interface

Let's look at how we might test the `matches` method in Product:

```java
@Test
void shouldMatchExactProductName() {
    // Arrange
    Product product = new Product("Test Product", "TEST123", 9.99);
    
    // Act
    boolean result = product.matches("Test Product");
    
    // Assert
    assertTrue(result);
}

@Test
void shouldNotMatchDifferentName() {
    // Arrange
    Product product = new Product("Test Product", "TEST123", 9.99);
    
    // Act
    boolean result = product.matches("Different Name");
    
    // Assert
    assertFalse(result);
}
```

### Testing Tips

1. **Choose Your Testing Approach**
   - Single Behavior Tests: Best for simple behaviors and easy failure diagnosis
   - Related Behaviors Tests: Best for tightly coupled behaviors and state transitions
   - Consider the complexity and relationship of behaviors when choosing
   - When in doubt, choose single behavior tests - they're easier to maintain and debug, both for the tests themselves and the code they're testing

2. **Use Descriptive Names**
   - Test names should explain what's being tested
   - Example: `shouldMatchExactProductName` instead of `testName`
   - For single behaviors, use names that focus on the specific outcome
   - For related behaviors, consider using names that reflect the relationship

3. **Follow Arrange-Act-Assert**
   - Arrange: Set up your test data
   - Act: Call the method being tested
   - Assert: Verify the results
   - For single behaviors, keep setup minimal and focused
   - For related behaviors, consider using comments to separate different phases

4. **Test Edge Cases**
   - Null values
   - Empty strings
   - Boundary conditions
   - Invalid inputs
   - For single behaviors, create separate tests for each edge case
   - For related behaviors, consider whether edge cases should be separate tests or part of a related behaviors test

5. **Keep Tests Independent**
   - Each test should work on its own
   - Don't rely on state from other tests
   - Use `@BeforeEach` for common setup
   - For single behaviors, ensure setup is complete and self-contained
   - For related behaviors, ensure each phase builds on the previous one clearly

## Step 7: Write Test Cases

Now that you understand how to write effective tests, it's time to put that knowledge into practice! You'll be creating two test files to validate your implementations of the `Searchable` interface:

1. `ProductTest.java`
2. `TransactionTest.java`
3. `ProductCatalogTest.java`
4. `RegisterTest.java`

Use the skeleton code below to get you started on these two files, then implement all the necessary test methods to thoroughly validate your Searchable interface implementations.

:::info
📦 **Package Location Note**

In industry, test classes are typically placed in the same package as the classes they're testing. For example, if you're testing `jam05.Product`, the test would be in `jam05.ProductTest`. However, for grading purposes in this course, you must place these tests in the `jam07` package. This is an exception to standard practice, but it helps us grade your work correctly.

:::

Create test files for both implementations in the `jam07` folder:

### `ProductTest.java`

This is mostly set up to test seperate behaviours, however the last method combines some closely related tests.  A few methods have been implemented for you.  To save space, TODO comments were not left in every empty method.

```java
package jam07;

import static org.junit.jupiter.api.Assertions.*;

import jam05.Product;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ProductTest {
    private Product product;

    @BeforeEach
    void setUp() {
        // TODO: Initialize test product(s)
    }
    @Test
    void shouldMatchExactProductName() {
        // Arrange: Set up a query with a known name
        String query = "Test Product";

        // Act: Call matches() with the query
        boolean result = product.matches(query);

        // Assert: Verify the result is what you expect
        assertTrue(result);
    }

    @Test
    void shouldMatchPartialName() {
        // Arrange: Set up a query with a known name

        
        // Act: Call matches() with the query

        
        // Assert: Verify the result is what you expect

    }

    @Test
    void shouldMatchExactSku() {}

    @Test
    void shouldMatchPartialSku() {}

    @Test
    void shouldNotMatchDifferentName() {}

    @Test
    void shouldHandleNullQuery() {}

    @Test
    void shouldHandleEmptyQuery() {}

    @Test
    void shouldHandleWhitespaceInQuery() {
        // Arrange: Set up a query with extra whitespace
        String query = "  Test Product  ";

        // Act: Call matches() with the query
        boolean result = product.matches(query);

        // Assert: Verify the result is what you expect
        assertTrue(result);
    }

    @Test
    void shouldMatchCaseInsensitive() {
        // Arrange: Set up queries with different cases
        String query1 = "test product";
        String query2 = "TEST PRODUCT";
        String query3 = "TeSt PrOdUcT";

        // Act: Call matches() with each query
        boolean result1 = product.matches(query1);
        boolean result2 = product.matches(query2);
        boolean result3 = product.matches(query3);

        // Assert: Verify all results are true
        assertTrue(result1);
        assertTrue(result2);
        assertTrue(result3);
    }
}
```

### `TransactionTest.java`

```java
package jam07;

import static org.junit.jupiter.api.Assertions.*;

import jam05.Product;
import jam06.Transaction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class TransactionTest {
    private Transaction transaction;
    private Product testProduct;

    @BeforeEach
    void setUp() {
        // TODO: Initialize transaction(s) and test product(s)
    }

    // TODO: Write tests for matches() method
    // Consider testing:
    // - State matching
    // - Amount matching
    // - Timestamp matching
    // - Case sensitivity matching
    // - Null/empty handling matching
    // I ended up with 9 tests.  You may have more or less!
}
```

### DETOUR❗

Next we are going to test the ProductCatalog and Register methods we wrote.  

While we could write ProductCatalogTest to look like:

```plantuml
@startuml
hide empty members

class ProductTest {
    +shouldMatchExactProductName()
    +shouldMatchPartialName()
    +shouldMatchExactSku()
    +shouldMatchPartialSku()
    +shouldNotMatchDifferentName()
    +shouldHandleNullQuery()
    +shouldHandleEmptyQuery()
    +shouldHandleWhitespaceInQuery()
    +shouldMatchCaseInsensitive()
}

class ProductCatalogTest {
    +shouldMatchExactProductName()
    +shouldMatchPartialName()
    +shouldMatchExactSku()
    +shouldMatchPartialSku()
    +shouldNotMatchDifferentName()
    +shouldHandleNullQuery()
    +shouldHandleEmptyQuery()
    +shouldHandleWhitespaceInQuery()
    +shouldMatchCaseInsensitive()
}

ProductTest -[hidden]down- ProductCatalogTest

note right of ProductCatalogTest
  Duplicates all tests from ProductTest
  Redundant testing
  Harder to maintain
end note
@enduml
```

Or we could use the testing hierarchy approach:

```plantuml
@startuml
hide empty members

class ProductTest {
    +shouldMatchExactProductName()
    +shouldMatchPartialName()
    +shouldMatchExactSku()
    +shouldMatchPartialSku()
    +shouldNotMatchDifferentName()
    +shouldHandleNullQuery()
    +shouldHandleEmptyQuery()
    +shouldHandleWhitespaceInQuery()
    +shouldMatchCaseInsensitive()
}

note right of ProductTest
  Tests detailed matching logic
  Covers all edge cases
  Focuses on single object behavior
end note

ProductTest -[hidden]down- ProductCatalogTest

class ProductCatalogTest {
    +shouldFindMultipleMatchingProducts()
    +shouldReturnEmptyListForNoMatches()
    +shouldReturnEmptyListForNullQuery()
}

note right of ProductCatalogTest
  Tests collection behavior
  Verifies use of Product.matches()
  Focuses on multiple objects
  Allows for the edge case tests of the Product.matches() to stay in the ProductTest file
end note
@enduml
```

:::info
💡 **Testing Hierarchy**

The test classes follow a clear hierarchy:

1. **ProductTest & TransactionTest**
   - Test the detailed implementation of `matches()`
   - Cover all edge cases and matching scenarios
   - Focus on the specific matching logic

2. **ProductCatalogTest & RegisterTest**
   - Test only the collection-level behavior
   - Verify correct use of the underlying `matches()` method
   - Focus on handling multiple items and edge cases

This approach:

- Avoids redundant testing
- Makes test failures easier to diagnose
- Follows the principle of testing at the most specific level
- Keeps each test class focused on its unique responsibilities

:::

### `ProductCatalogTest.java`

Note: All but one method is implemented for you.  You'll need to implement the next to last one.

```java
package jam07;

import static org.junit.jupiter.api.Assertions.*;

import jam05.Product;
import jam05.ProductCatalog;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ProductCatalogTest {
    private ProductCatalog catalog;
    private Product product1;
    private Product product2;
    private Product product3;

    @BeforeEach
    void setUp() {
        catalog = new ProductCatalog();
        product1 = new Product("Red Apple", "FRUIT001", 0.99);
        product2 = new Product("Green Apple", "FRUIT002", 0.89);
        product3 = new Product("Banana", "FRUIT003", 0.79);
        
        catalog.addProduct(product1);
        catalog.addProduct(product2);
        catalog.addProduct(product3);
    }

    @Test
    void shouldFindMultipleMatchingProducts() {
        // Arrange: Set up a query that matches multiple products
        String query = "Apple";
        
        // Act: Call searchProducts with the query
        List<Product> results = catalog.searchProducts(query);
        
        // Assert: Verify the result contains both apples but not the banana
        assertEquals(2, results.size());
        assertTrue(results.contains(product1));
        assertTrue(results.contains(product2));
        assertFalse(results.contains(product3));
    }

    @Test
    void shouldReturnEmptyListForNoMatches() {
        // TODO: Implement this test
        // Arrange: Set up a query that matches no products

        // Act: Call searchProducts with the query

        // Assert: Verify the result is an empty list
    }

    @Test
    void shouldReturnEmptyListForNullQuery() {
        // Arrange: Use null as the query
        String query = null;
        
        // Act: Call searchProducts with null
        List<Product> results = catalog.searchProducts(query);
        
        // Assert: Verify an empty list is returned
        assertTrue(results.isEmpty());
    }
}
```

### `RegisterTest.java`

🔧 **Helper Methods in Test Setup**

This test class introduces helper methods to make the setup code more organized and reusable:

1. `initializeCashDrawer()`: Sets up the register's cash drawer with test money
2. `completeTransaction()`: Creates and completes a transaction with a test product

These helper methods:

- Reduce code duplication
- Make the setup code more readable
- Make it easier to modify test setup in one place
- Follow the DRY (Don't Repeat Yourself) principle

You can use this same pattern in your own test classes to make them more maintainable!

🔢 **Static Register Counter**

The test class also introduces a static counter (`registerCounter`) to ensure each test gets a unique register name. This is important because:

- The Register class uses a static factory method (`createRegister`)
- Each register must have a unique name
- Multiple tests running in parallel could create name conflicts
- The counter ensures names like "TEST-001", "TEST-002", etc.

This is a common pattern when testing classes that use static factory methods or require unique identifiers.

:::warning
⚠️ **Method Call Changes**

Just like in Jam06's RegisterDriver, you may need to make changes to method calls to resolve all the errors. The your Register class's API is probably different from mine, so you'll need to update any method calls accordingly.  However, you shouldn't change the test values.
:::

```java
package jam07;

import static org.junit.jupiter.api.Assertions.*;

import jam05.Product;
import jam05.ProductCatalog;
import jam06.Money;
import jam06.Register;
import jam06.Transaction;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


class RegisterTest {
    private static int registerCounter = 0;
    private Register register;
    private Product testProduct;
    private Transaction transaction1;
    private Transaction transaction2;

    @BeforeEach
    void setUp() {
        // Set up catalog and register
        ProductCatalog catalog = new ProductCatalog();
        testProduct = new Product("Test Product", "TEST123", 9.43);
        catalog.addProduct(testProduct);
        
        // Create register with unique name
        String registerName = String.format("TEST-%03d", ++registerCounter);
        register = Register.createRegister(registerName, catalog);
        
        // Initialize cash drawer
        initializeCashDrawer();

        // Complete two transactions
        transaction1 = completeTransaction();
        transaction2 = completeTransaction();
    }

    private void initializeCashDrawer() {
        // Add money denominations individually to avoid null issues
        register.addMoneyToDrawer(Money.TWENTY, 5);   // $100
        register.addMoneyToDrawer(Money.TEN, 10);     // $100
        register.addMoneyToDrawer(Money.FIVE, 20);    // $100
        register.addMoneyToDrawer(Money.ONE, 50);     // $50
    }

    private Transaction completeTransaction() {
        register.startTransaction();
        register.addProductToTransaction(testProduct.getSku());
        Transaction thisTransaction = register.getCurrentTransaction();
        register.processPayment(Map.of(Money.TEN, 1));  // $10 payment for $9.00 item
        return thisTransaction;
    }

    @Test
    void shouldReturnEmptyListForNoMatches() {
        // TODO: Implement this test
        // Arrange: Set up a query that matches no transactions
        // (Hint: Transaction.matches() checks state, amount, and timestamp)

        // Act: Call searchTransactions with the query

        // Assert: Verify the result is an empty list
    }

    @Test
    void shouldFindMultipleMatchingTransactions() {
        // Arrange: Set up a query that matches both transactions
        String query = "$10.00";  // Search by amount

        // Act: Search for transactions
        List<Transaction> results = register.searchTransactions(query);

        // Assert: Both transactions should be found
        assertEquals(2, results.size());
        assertTrue(results.contains(transaction1));
        assertTrue(results.contains(transaction2));
    }

    @Test
    void shouldIncludeCurrentTransactionInSearch() {
        // Arrange: Start a new transaction that matches search
        register.startTransaction();
        register.addProductToTransaction(testProduct.getSku());
        Transaction currentTransaction = register.getCurrentTransaction();
        String query = "IN_PROGRESS";  // Search by state

        // Act: Search for transactions
        List<Transaction> results = register.searchTransactions(query);

        // Assert: Current transaction should be included
        assertTrue(results.contains(currentTransaction));
    }

    @Test
    void shouldReturnEmptyListForNullQuery() {
        // Arrange: Use null as the query
        String query = null;

        // Act: Search with null query
        List<Transaction> results = register.searchTransactions(query);

        // Assert: Should return empty list
        assertTrue(results.isEmpty());
    }
}
```

:::info
💡 **Testing Tips**

- Follow the Arrange-Act-Assert pattern in your tests
- Test one specific behavior per test method
- Use clear, descriptive test method names
- Consider edge cases (null values, empty strings, etc.)
- Test both positive and negative cases
- Use appropriate assertions for the type of test

:::

## Exercise 1 Checkpoint

> 🔍 **Checkpoint**
>
> Before proceeding, verify:
>
> **Interface Implementation**
>
> - `Searchable` interface is properly defined
> - Both `Product` and `Transaction` implement the interface
> - `ProductCatalog` uses the interface for searching
> - Generic `searchList` method is implemented in `Register`
> - `searchTransactions` method is implemented in `Register`
>
> **Testing Implementation**
>
> - All test cases are implemented and pass
> - Tests follow the hierarchy (unit tests vs collection tests)
> - Tests use helper methods and static counters appropriately
> - Tests follow Arrange-Act-Assert pattern
> - Tests cover both normal and edge cases
>
> **Code Quality**
>
> - Documentation is complete
> - Code follows Java naming conventions
> - All files are in the correct packages
> - All changes are committed

## Save Your Work - Exercise 1

**Run all tests to verify your implementation**:

```bash
# Right click on the test folder in IntelliJ
# Click "Run Tests in 'csci205_jams'"
# Ensure all tests pass
```

**Stage your changes**:

```bash
# Jam05 files
git add src/main/java/jam05/Product.java src/main/java/jam05/ProductCatalog.java
# Jam06 files
git add src/main/java/jam06/Register.java src/main/java/jam06/Transaction.java
# Jam07 files
git add src/main/java/jam07/Searchable.java src/test/java/jam07/ProductCatalogTest.java src/test/java/jam07/ProductTest.java src/test/java/jam07/RegisterTest.java src/test/java/jam07/TransactionTest.java
```

**Verify what files are uncommitted**:

```bash
git status
```

**Commit your work**:

```bash
git commit -m "jam07: Implement Searchable interface in Product and Transaction"
```

:::success
🔑 **Key Takeaways**

- Interfaces define behavior contracts
- Different classes can implement the same interface differently
- Interfaces enable polymorphic behavior
- Good tests verify both common and unique behaviors
- Documentation helps others understand your design decisions

:::
