Unit test and test double

Ruby on Rails with Rspec

Posted by Theo Cha on August 15, 2022

Fail first, I am not afraid of failing. I am saying this in my real life. For example:

Uncle Bob

Before starting to say anything about testing, I can’t forget to say about Uncle bob because I have learned TDD from his book – ‘Clean code’.

The Three Rules of TDD

Robert C. Martin (“Uncle Bob”) provides a concise set of rules for practicing TDD.

  • Write production code only to pass a failing unit test.
  • Write no more of a unit test than sufficient to fail (compilation failures are failures).
  • Write no more production code than necessary to pass the one failing unit test.

With above those three principles, I would say we understand many parts of TDD.

TDD benefits


When I see the question, the first thing that comes to my brain is that writing tests make me comfortable and confident in what I am doing. Especially when a team works together, it makes many things easier. There are still many reasons why we write tests.

  1. Writing the tests first requires you to really consider what you want from the code.
  2. You receive fast feedback.
  3. TDD creates a detailed specification.
  4. TDD reduces time spent on rework.
  5. You spend less time in the debugger.
  6. You are able to identify the errors and problems quickly.
  7. TDD tells you whether your last change (or refactoring) broke previously working code.
  8. TDD allows the design to evolve and adapt to your changing understanding of the problem.
  9. TDD forces the radical simplification of the code. You will only write code in response to the requirements of the tests.
  10. You’re forced to write small classes focused on one thing.
  11. TDD creates SOLID code.
  12. 20 benefits of TDD

Unit tests, mocks, fakes, stubs.


Why Unit Test? To test the functionality of out code rather than finding bug that may exist in our system.

Unit Test are

  • Tests written for testing a unit of code
  • One unit test runs independently
  • External dependencies are managed with doubles(mock/fakes/stubs)
  • Should complete within milliseconds

Writing unit tests in Rails


Arrange – Act – Assert (AAA)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def sum(a,b)
  return a + b
end

it '.sum' do
  //Arrange
  first = 100
  second = 200

  //Act
  result = sum(first, second)

  //Assert
  expect(result).to eq(first+second)
end

Test double

  • We Used in External dependencies where may cause side effect
  • Mocks, Fakes, Stubs

Stub

Stub Illustration | Credit: Michal Lipski

  • Generates predefined outputs
  • Programmed stub to return a success, failure, or exception
  • What to test?
  • Returns success, failure, or exception (as coded)
  • Checks the behavior of code under test in case of these return values
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class GradesService {
    private final Gradebook gradebook;

    public GradesService(Gradebook gradebook) {
        this.gradebook = gradebook;
    }

    Double averageGrades(Student student) {
        return average(gradebook.gradesFor(student));
    }
}

public class GradesServiceTest {
    private Student student;
    private Gradebook gradebook;

    @Before
    public void setUp() throws Exception {
        gradebook = mock(Gradebook.class);
        student = new Student();
    }

    @Test
    public void calculates_grades_average_for_student() {
        when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook
        double averageGrades = new GradesService(gradebook).averageGrades(student);
        assertThat(averageGrades).isEqualTo(8.0);
    }
}

Source | Michal Lipski


Mock

Mock Illustration | Credit: Michal Lipski

  • Mocks replaces external interface
  • Mocks are not used for checking function’s behavior or return values from the function call
  • What to test?
  • Mock function called or not?
  • How many times it is called?
  • What params are passed?
  • When was it called?
  • Right call, right number of times with right params.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SecurityCentral {
    private final Window window;
    private final Door door;

    public SecurityCentral(Window window, Door door) {
        this.window = window;
        this.door = door;
    }

    void securityOn() {
        window.close();
        door.close();
    }
}

Source | Michal Lipski


Fake

Fake Illustration | Credit: Michal Lipski

  • Almost working implementation
  • Connect to a local HTTP server
  • What to test?
  • Instead of going to the internet it connects to a local implementation
  • Check the behavior with respect to the actual data it receives from the server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Profile("transient")
public class FakeAccountRepository implements AccountRepository {

       Map<User, Account> accounts = new HashMap<>();

       public FakeAccountRepository() {
              this.accounts.put(new User("john@bmail.com"), new UserAccount());
              this.accounts.put(new User("boby@bmail.com"), new AdminAccount());
       }

       String getPasswordHash(User user) {
              return accounts.get(user).getPasswordHash();
       }
}

Source | Michal Lipski

References