Skip to content

Instantly share code, notes, and snippets.

@thieux
Last active August 9, 2017 09:15
Show Gist options
  • Save thieux/075536cc9023901060ebf06a3a14ecdf to your computer and use it in GitHub Desktop.
Save thieux/075536cc9023901060ebf06a3a14ecdf to your computer and use it in GitHub Desktop.
State versus interaction - design strategies & tests

Pattern

The input (that feeds a function) can be implemented in 2 ways: controlled by the caller or by the callee.

The outputs (that is provided by a function) can be implemented in 2 ways: exposed to the caller or by message.

Input as parameter

The function's caller controls the input.

class Foo {
  void foo(Object arg) {
    ...
  }
}
class FooTest {
  @Test
  void test() {
    foo("bar");
  }
}

Implicit input

The function's callee controls the input.

class Foo {
  void foo() {
    Object arg = collaborator.get();
    ...
  }
}
class FooTest {
  @Test
  void test() {
    when(collaborator.get()).thenReturn("bar");
    foo();
  }
}

Output as return

The output is exposed to the caller via a return value.

class Foo {
  Object foo() {
    return "bar";
  }
}
class FooTest {
  @Test
  void test() {
    Object actual = foo();
    assertEquals("bar", actual);
  }
}

Implicit output

The outputs are sent by message to some collaborators.

class Foo {
  void foo() {
    collaborator.set("bar");
  }
}
class FooTest {
  @Test
  void test() {
    foo();
    verify(collaborator).set("bar");
  }
}

Examples

No parameter & no return

class Archiver {
  /**
  * Delete if obsolete.
  * A file is obsolete when its last modification is older than today.
  */
  void clean(String path) {
    LocalDate last = fileSystem.getLastModificationTime(path);
    LocalDate now = clock.now();
    if (isBefore(last, now)) {
      fileSystem.delete(path);
    }
  }
}

class ArchiverTest {
  @Test
  void keep_recent_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("20/12/2017"));
    when(clock.now()).thenReturn(new LocalDate("20/12/2017"));
    
    // when
    clean("/tmp/foo");
    
    // then
    verify(fileSystem, never()).delete(anyString());
  }
  
    @Test
  void delete_old_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("19/12/2017"));
    when(clock.now()).thenReturn(new LocalDate("20/12/2017"));
    
    // when
    clean("/tmp/foo");
    
    // then
    verify(fileSystem).delete("/tmp/foo");
  }
}

Return value

class Archiver {
  /**
  * Delete if obsolete.
  * A file is obsolete when its last modification is older than today.
  */
  boolean clean(String path) {
    LocalDate last = fileSystem.getLastModificationTime(path);
    LocalDate now = clock.now();
    if (isBefore(last, now)) {
      return true;
    }
    return false;
  }
}

class ArchiverTest {
  @Test
  void keep_recent_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("20/12/2017"));
    when(clock.now()).thenReturn(new LocalDate("20/12/2017"));
    
    // when
    boolean actual = clean("/tmp/foo");
    
    // then
    assertFalse(actual);
  }
  
    @Test
  void delete_old_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("19/12/2017"));
    when(clock.now()).thenReturn(new LocalDate("20/12/2017"));
    
    // when
    boolean actual = clean("/tmp/foo");
    
    // then
    assertTrue(actual);
  }
}

Parameter

class Archiver {
  /**
  * Delete if obsolete.
  * A file is obsolete when its last modification is older than today.
  */
  void clean(String path, LocalDate now) {
    LocalDate last = fileSystem.getLastModificationTime(path);
    if (isBefore(last, now)) {
      fileSystem.delete(path);
    }
  }
}

class ArchiverTest {
  @Test
  void keep_recent_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("20/12/2017"));
    
    // when
    clean("/tmp/foo", new LocalDate("20/12/2017"));
    
    // then
    verify(fileSystem, never()).delete(anyString());
  }
  
    @Test
  void delete_old_file() {
    // given
    when(fileSystem.getLastModificationTime("/tmp/foo")).thenReturn(new LocalDate("19/12/2017"));
    
    // when
    clean("/tmp/foo", new LocalDate("20/12/2017"));
    
    // then
    verify(fileSystem).delete("/tmp/foo");
  }
}
@karasco
Copy link

karasco commented Aug 9, 2017

Readable article which reconciles simplicity and clarity !!! Good job @thieux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment