Skip to content

Commit

Permalink
dt load and saver docs
Browse files Browse the repository at this point in the history
  • Loading branch information
isidore committed May 17, 2023
1 parent c958bb2 commit 9d9e617
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,97 @@

import com.spun.util.persistence.Loader;
import com.spun.util.persistence.Saver;
import org.approvaltests.reporters.UseReporter;
import org.approvaltests.reporters.macosx.DiffMergeReporter;
import org.junit.Assert;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@UseReporter(DiffMergeReporter.class)
public class LoaderTest {
public static class Step1 {
// begin-snippet: separating_loaders_1
public void reserveItems(List<String> ids) {
Item[] items = getInventory();
for (Item item : items) {
if (ids.contains(item.id) && item.inventoryCount > 0) {
registerHold(item);
}
}
}
// end-snippet
}

public static class Step2 {
// begin-snippet: separating_loaders_2
public void reserveItems(List<String> ids) {
reserveItems(ids, new InventoryLoader(), new ItemReserver());
}

public void reserveItems(List<String> ids, Loader<Item[]> loader, Saver<Item> itemReserver) {
Item[] items = loader.load();
for (Item item : items) {
if (ids.contains(item.id) && item.inventoryCount > 0) {
itemReserver.save(item);
}
}
}
// end-snippet
// begin-snippet: seperating_loaders_test

@Test
public void name() {

Item milk = new Item("M101", "Milk", 2);
Item missing_item = new Item("W202", "Item not Found", 2);
Item sold_out_item = new Item("S303", "SuperPopularGame", 0);

public class LoaderTest
{
public static class Step1
{
// begin-snippet:separating_loaders_1
public void reserveItems(List<String> ids)
{
Item[] items = getInventory();
for (Item item : items)
{
if (ids.contains(item.id) && item.inventoryCount > 0)
{
registerHold(item);
List<Item> saved = new ArrayList<>();
reserveItems(Arrays.asList(milk.id, missing_item.id, sold_out_item.id ),
() -> new Item[]{milk, sold_out_item},
i -> {saved.add(i); return i;});

// Only reserved milk
Assert.assertArrayEquals(saved.toArray(), new Item[]{milk});
}
}

// end-snippet
}
// end-snippet
}
public static class Step2
{
// begin-snippet:separating_loaders_2
public void reserveItems(List<String> ids)
{
reserveItems(ids, new InventoryLoader(), new ItemReserver());

public static class InventoryLoader implements Loader<Item[]> {
@Override
public Item[] load() {
return getInventory();
}
}
public void reserveItems(List<String> ids, Loader<Item[]> loader, Saver<Item> itemReserver)
{
Item[] items = loader.load();
for (Item item : items)
{
if (ids.contains(item.id) && item.inventoryCount > 0)
{
itemReserver.save(item);

public static class ItemReserver implements Saver<Item> {
@Override
public Item save(Item save) {
registerHold(save);
return save;
}
}
}
// end-snippet
public static class InventoryLoader implements Loader<Item[]>
{
@Override
public Item[] load()
{
return getInventory();
}

private static void registerHold(Item item) {
}
public static class ItemReserver implements Saver<Item>
{
@Override
public Item save(Item save)
{
registerHold(save);
return save;
}

private static Item[] getInventory() {
Item[] items = new Item[0];
return items;
}

public static class Item {
public String id;
public int inventoryCount;
public String name;

public Item(String id, String name, int inventoryCount) {
this.id = id;
this.inventoryCount = inventoryCount;
this.name = name;
}
}
}
private static void registerHold(Item item)
{
}
private static Item[] getInventory()
{
Item[] items = new Item[0];
return items;
}
private class Item
{
public String id;
public int inventoryCount;
}
}
83 changes: 78 additions & 5 deletions approvaltests/docs/how_to/EncapsulatingWithLoadersAndSavers.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<a id="top"></a>

# How to encapsulate your database calls
# How to Encapsulate Your Database Calls

<!-- toc -->
## Contents

* [The problem](#the-problem)
* [The scenario](#the-scenario)
* [The solution](#the-solution)<!-- endToc -->
* [Starting Example](#starting-example)
* [The solution](#the-solution)
* [Why this is better](#why-this-is-better)
* [Example Test](#example-test)<!-- endToc -->


## The problem
Expand All @@ -29,11 +32,81 @@ Code->>Database:Put item on hold
Database->>Code:Confirmation
```

### Starting Example
The code looks like:
snippet:separating_loaders_1
<!-- snippet: separating_loaders_1 -->
<a id='snippet-separating_loaders_1'></a>
```java
public void reserveItems(List<String> ids) {
Item[] items = getInventory();
for (Item item : items) {
if (ids.contains(item.id) && item.inventoryCount > 0) {
registerHold(item);
}
}
}
```
<sup><a href='/approvaltests-tests/src/test/java/org/approvaltests/demos/LoaderTest.java#L16-L25' title='Snippet source file'>snippet source</a> | <a href='#snippet-separating_loaders_1' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


## The solution
### The solution

Refactor it to include loaders and savers
snippet:separating_loaders_2
<!-- snippet: separating_loaders_2 -->
<a id='snippet-separating_loaders_2'></a>
```java
public void reserveItems(List<String> ids) {
reserveItems(ids, new InventoryLoader(), new ItemReserver());
}

public void reserveItems(List<String> ids, Loader<Item[]> loader, Saver<Item> itemReserver) {
Item[] items = loader.load();
for (Item item : items) {
if (ids.contains(item.id) && item.inventoryCount > 0) {
itemReserver.save(item);
}
}
}
```
<sup><a href='/approvaltests-tests/src/test/java/org/approvaltests/demos/LoaderTest.java#L29-L42' title='Snippet source file'>snippet source</a> | <a href='#snippet-separating_loaders_2' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Why this is better

The solution here uses Loaders and Savers to allow for Dependency Injection (DI). The goal of DI is to make code more flexible, modular, and testable by removing hard-coded dependencies.

In the original code, the `reserveItems` method was directly tied to the database through the `getInventory` and `registerHold` methods. This means that any time you wanted to test this function, you would need a live database connection, which is not ideal for unit testing.

The refactored code, however, separates the responsibilities of loading inventory items and reserving them into separate components (`Loader` and `Saver` interfaces respectively). This allows the actual method of loading and saving items to be abstracted away from the `reserveItems` method itself.

The `reserveItems` method now takes in instances of Loader and Saver as parameters. In a live environment, these could be instances that interact with a live database. However, when unit testing, you can pass in fake instances of Loader and Saver (usually a lambda) removing the need for a live database connection during testing.

The end state improves testability, flexibility, and code management by removing direct database dependencies.

## Example Test

The resulting separation allows it to be easily tested.
Here's how:

<!-- snippet: seperating_loaders_test -->
<a id='snippet-seperating_loaders_test'></a>
```java
@Test
public void name() {

Item milk = new Item("M101", "Milk", 2);
Item missing_item = new Item("W202", "Item not Found", 2);
Item sold_out_item = new Item("S303", "SuperPopularGame", 0);

List<Item> saved = new ArrayList<>();
reserveItems(Arrays.asList(milk.id, missing_item.id, sold_out_item.id ),
() -> new Item[]{milk, sold_out_item},
i -> {saved.add(i); return i;});

// Only reserved milk
Assert.assertArrayEquals(saved.toArray(), new Item[]{milk});
}
```
<sup><a href='/approvaltests-tests/src/test/java/org/approvaltests/demos/LoaderTest.java#L43-L61' title='Snippet source file'>snippet source</a> | <a href='#snippet-seperating_loaders_test' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

0 comments on commit 9d9e617

Please sign in to comment.