Deniss M. Deniss M. - 3 months ago 29
Java Question

spring-boot: test @service class

I am a bit new to Spring Boot and testing. I want to test this @Service class:

@Service
public class TransactionService {

private MessagingService messagingService;
private CustomerRepository customerRepository;

private static final Logger log = LoggerFactory.getLogger(TransactionService.class);

@Autowired
public TransactionService(MessagingService messagingService, CustomerRepository customerRepository){
Assert.notNull(messagingService, "MessagingService cannot be null");
this.messagingService = messagingService;
Assert.notNull(customerRepository, "CustomerRepository cannot be null");
this.customerRepository = customerRepository;
}

public void makeTransactionFromSenderToReceiver(Customer sender, Customer receiver, int amount) {

if (sender.getBalance() >= amount) {
sender.setBalance(sender.getBalance() - amount);
receiver.setBalance(receiver.getBalance() + amount);

customerRepository.save(sender);
customerRepository.save(receiver);
}

else {
throw new CustomerBalanceExceededException();
}
}
}


In my arsenal I have spring-boot-starter-test maven dependency for testing. I managed to test my Customer class like this:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class CustomerTests
{
@Autowired
private CustomerRepository repository;

@Test
public void testCreate()
{
final Customer customer = new Customer();
customer.setName("CoolGuy");

Assert.notNull(repository.save(customer));
}
}


I currently have no idea how to test the function of the method of the @Service class defined above. Any hints much appreciated.

EDIT: I have tried writing a test, but I do not think it is the correct way to test this logic. Any help would be appreciated:

TransactionServiceTests.class

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class TransactionServiceTests {

@Autowired
private CustomerRepository repository;

@Autowired
private TransactionService transactionService;

@Test
public void testTransactionBetweenCustomersAndBalanceOfReceiver() {

int AMOUNT = 50;

Customer customerOksana = repository.findByName("Oksana");
Customer customerDeniss = repository.findByName("Deniss");

transactionService.makeTransactionFromSenderToReceiver(customerDeniss, customerOksana, AMOUNT);
assertThat(customerOksana.getBalance()).isEqualTo(customerOksana.getBalance());
}

@Test
public void testTransactionBetweenCustomersAndBalanceOfSender() {

int AMOUNT = 40;

Customer customerOksana = repository.findByName("Oksana");
Customer customerDeniss = repository.findByName("Deniss");

transactionService.makeTransactionFromSenderToReceiver(customerDeniss, customerOksana, AMOUNT);
assertThat(customerDeniss.getBalance()).isEqualTo(customerDeniss.getBalance());
}
}

Answer

Unit testing, please see Mockito or some other mocking framework. You shouldn't even require any annotations on the test class itself. You want to test the method call and verify that the interactions with the other classes and the results are what you expect.

Here is an example of a unit test using Mockito. Obviously you would want to flesh this out and add more tests. This would provide you coverage on your code, and validate pre and post conditions. This won't test that the repository code is mapped properly or the transaction boundaries work as expected, that would have to be done using an integration test.

public class TransactionServiceTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    private MessagingService mockMessagingService;
    @Mock
    private CustomerRepository mockCustomerRepository;

    @Test
    public void testMakeTransactionFromSenderToReceiver() {
        // setup the state of the sender and receiver
        Customer sender = new Customer();
        sender.setBalance(5.0);
        Customer receiver = new Customer();
        receiver.setBalance(10.0);
        TransactionService transactionService = new TransactionService(mockMessagingService, mockCustomerRepository);

        transactionService.makeTransactionFromSenderToReceiver(sender, receiver);

        // assert the expected state of the Customer objects
        assertEquals(1.0, sender.getBalance());
        assertEquals(1.0, receiver.getBalance());

        // verify that the repositories were called appropriately once
        // this can be made to be more specific and check the argument that was passed - see Mockito documentation
        verify(mockCustomerRepository, times(2)).save(any());
    }

}

Your logic for the assertion is incorrect

@Test
public void testTransactionBetweenCustomersAndBalanceOfReceiver() {

    int AMOUNT = 50;

    Customer customerOksana = repository.findByName("Oksana");
    Customer customerDeniss = repository.findByName("Deniss");

    transactionService.makeTransactionFromSenderToReceiver(customerDeniss, customerOksana, AMOUNT);
    assertThat(customerOksana.getBalance()).isEqualTo(customerOksana.getBalance());
}

You are asserting that the same instance has the same balance. This will always be true. You want to check to see if the adjustment is what you expect either by saving a new customer to the repository or getting the original balance before executing your method makeTransactionFromSenderToReceiver.

@Test
public void testTransactionBetweenCustomersAndBalanceOfReceiver() {

    int AMOUNT = 50;

    // prepare your test data unless you always expect those values to exist.
    Customer customerReceiver = new Customer();
    customerReciever.setName("Oksana");
    customerReceiver.setBalance(12);
    customerReceiver = repository.save(customerReceiver);

    Customer customerSender = new Customer();
    customerSender.setName("Deniss");
    customerSender.setBalance(5);
    customerSender = repository.save(customerSender);

    // assign before executing method
    int expectedReceiverAmount = customerReceiver.getBalance() + AMOUNT;
    int expectedSenderAmount = customerSender.getBalance() - AMOUNT;
    transactionService.makeTransactionFromSenderToReceiver(customerSender, customerReceiver, AMOUNT);

    assertEquals(expectedSenderAmount, customerSender.getBalance());
    assertEquals(expectedReceiverAmount, customerReceiver.getBalance());
}