Home > Software engineering >  Simple JUnit test fails
Simple JUnit test fails

Time:04-08

I want to learn how to write JUnit tests and I fail completely.

This is my test:

    @Test
    public void testGetAllCustomers() {
        // given
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(c1);
        customerList.add(c2);
        customerList.add(c3);
        given(customerRepository.findAll()).willReturn(customerList);
            
        // when
        List<Customer> resultList = customerService.getAllCustomers();
        
        // then
        assertThat(resultList).hasSize(3);
    }

The problem is that this simple assertion already fails. The returned list is empty. I know, I am new to all of this, but the failure is so unexpected from my point of view that I have no approach on how to solve the problem.

This is the whole code (not that much):

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class CustomerServiceTests {
    
    @Mock
    private CustomerRepository customerRepository;
    
    @InjectMocks
    private CustomerService customerService;
    
    private Customer c1 = new Customer(
            1L, 
            "Hans Meyer", 
            false, 
            Timestamp.from(Instant.now()),
            null,
            null
    );
    private Customer c2 = new Customer(
            2L, 
            "Marie Hollande", 
            true, 
            Timestamp.from(Instant.now()),
            null,
            null
    );
    private Customer c3 = new Customer(
            3L, 
            "Mohammed Abbas", 
            false, 
            Timestamp.from(Instant.now()),
            null,
            null
    );
    
    @Before
    public void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testGetAllCustomers() {
        // given
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(c1);
        customerList.add(c2);
        customerList.add(c3);
        given(customerRepository.findAll()).willReturn(customerList);
        
        // when
        List<Customer> resultList = customerService.getAllCustomers();
        
        // then
        assertThat(resultList).hasSize(3);
    }
}

The function to test is just this and I know that it works:

public List<Customer> getAllCustomers() {
        return customerRepository.findAll();
}

Actually, I just want to learn how to write such tests, but for days I have been failing to write one. There are quite a few examples and explanations, but no matter what I try, I have not yet managed a working test. How to create a working test for getAllCustomers()? Thank you for your help!

CodePudding user response:

You initialize your mocks twice - first by @RunWith(MockitoJUnitRunner.class) and then with MockitoAnnotations.openMocks(this);

This means that when you enter your setUp method:

  • customerRepository points to R1
  • customerService.customerRepository points to R1

And after this method

  • customerRepository points to R2
  • customerService.customerRepository points to R1

Which means that your service was not re-initialized

Use a debugger to confirm this observation.

@InjectMocks javadoc says:

MockitoAnnotations.openMocks(this) method has to be called to initialize annotated objects. In above example, openMocks() is called in @Before (JUnit4) method of test's base class. For JUnit3 openMocks() can go to setup() method of a base class. Instead you can also put openMocks() in your JUnit runner (@RunWith) or use the built-in MockitoJUnitRunner.

CodePudding user response:

Here's my recommendation: you should refactor your code and apply constructor-injection style, like this:

public class CustomerService {

  private final CustomerRepository customerRepository;

  public CustomerService(CustomerRepository customerRepository) {
    this.customerRepository = customerRepository;       // inject customerRepository via constructor
  }

  // ...

}

then, you don't need any Mockito annotations. Just remove them.

public class CustomerServiceTests {

    private CustomerRepository customerRepository; 
    private CustomerService customerService;

    // ... init Customer

    @Before
    public void setup() {
      customerRepository = mock(CustomerRepository.class); // inject mocked customerRepository via constructor
      customerService = new CustomerService(customerRepository);
    }

    @Test
    public void testGetAllCustomers() {
        // same as your test code
    }

}

You can see that CustomerServiceTests becomes very simple and trivial to test.

  • Related