0

I want to unit test rentBook method that is rensposible for renting.

Here is the @Service

package bookrental.service.book.rentals;

import bookrental.model.account.User;
import bookrental.model.book.Book;
import bookrental.model.book.BookRentals;
import bookrental.repository.account.UserRepository;
import bookrental.repository.book.BookRepository;
import bookrental.repository.book.BookRentalsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class BookRentalService {

    private final UserRepository userRepository;
    private final BookRepository bookRepository;
    private final BookRentalsRepository bookRentalsRepository;

    @Autowired
    public BookRentalService(BookRepository bookRepository, BookRentalsRepository bookRentalsRepository, UserRepository userRepository) {
        this.bookRepository = bookRepository;
        this.bookRentalsRepository = bookRentalsRepository;
        this.userRepository = userRepository;
    }

    public String rentBook(int userID, int bookID) {
        if (userRepository.doesAccountExistsWithGivenID(userID)) {
            if (bookRepository.doesBookExistsWithGivenID(bookID)) {
                Book bookToRent = bookRepository.findOne(bookID);
                if (bookToRent.isAvailable()) {
                    updateBookAvailabilityAndSaveToDb(bookToRent);
                    BookRentals preparedBookToRent = prepareBookToRent(userID, bookID);
                    bookRentalsRepository.save(preparedBookToRent);
                } else {
                    throw new IllegalArgumentException("Book is not available");
                }
            } else {
                throw new IllegalArgumentException("Book does not exist!");
            }
        } else {
            throw new IllegalArgumentException("Account does not exist!");
        }
        return "Book was rented";
    }

    private BookRentals prepareBookToRent(int userID, int bookID) {
        return new BookRentals(new Book(bookID), new User(userID));
    }

    private void updateBookAvailabilityAndSaveToDb(Book bookToRent) {
        bookToRent.setAvailable(false);
        bookRepository.save(bookToRent);
    }

    public List<BookRentals> findAllRentals() {
        List<BookRentals> rentedBooks = new ArrayList<>();
        bookRentalsRepository.findAll().forEach(rentedBooks::add);
        return rentedBooks;
    }
}

I tried to unit test it, but getting NPE. I do not know what I am doing wrong.

package bookrental.service.book.rentals;

import bookrental.model.account.User;
import bookrental.model.book.Book;
import bookrental.model.book.BookRentals;
import bookrental.repository.book.BookRentalsRepository;
import bookrental.repository.book.BookRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static org.junit.Assert.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class BookRentalServiceTest {

    @Mock
    BookRentalsRepository bookRentalsRepository;
    @Mock
    BookRepository bookRepository;

    @InjectMocks
    BookRentalService bookRentalService;

    @Test
    public void rentBook() {
        Book book = createDummyBook();
        User user = createDummyUser();
        BookRentals bookToRent = createDummyRentedBook(user, book);

       when(bookRentalsRepository.save(bookToRent)).thenReturn(bookToRent);

        bookRentalService.rentBook(user.getId(), book.getId());

        verify(bookRentalsRepository).save(bookToRent);
        verify(bookRentalService, times(1)).rentBook(user.getId(), book.getId());
    }

    @Test
    public void findAllRentals() {
    }

    private Book createDummyBook() {
        return new Book(0, "W pustyni i w puszczy", "Henryk Sienkiewicz", "dramat", true);
    }

    private BookRentals createDummyRentedBook(User user, Book book) {
        return new BookRentals(book, user);
    }

    private User createDummyUser() {
        return new User(0, "must", "123");
    }
}

Stacktrace:

java.lang.NullPointerException
    at bookrental.service.book.rentals.BookRentalService.rentBook(BookRentalService.java:30)
    at bookrental.service.book.rentals.BookRentalServiceTest.rentBook(BookRentalServiceTest.java:38)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

/// EDIT

@Test
    public void rentBook() {
        Book book = createDummyBook();
        User user = createDummyUser();
        BookRentals bookToRent = createDummyRentedBook(user, book);

        when(userRepository.doesAccountExistsWithGivenID(user.getId())).thenReturn(true);
        when(bookRepository.doesBookExistsWithGivenID(book.getId())).thenReturn(true);
        when(bookRepository.findOne(book.getId())).thenReturn(book);
        when(userRepository.findOne(user.getId())).thenReturn(user);

        String expected = "Book was rented";

        assertEquals(expected, bookRentalService.rentBook(user.getId(), book.getId()));

        verify(bookRentalsRepository, times(1)).save(bookToRent);
    }

STACKTRACE:

Argument(s) are different! Wanted:
bookRentalsRepository.save(
    bookrental.model.book.BookRentals@7574b32f
);
-> at bookrental.service.book.rentals.BookRentalServiceTest.rentBook(BookRentalServiceTest.java:48)
Actual invocation has different arguments:
bookRentalsRepository.save(
    bookrental.model.book.BookRentals@1c62c511
);
-> at bookrental.service.book.rentals.BookRentalService.rentBook(BookRentalService.java:36)

Comparison Failure:  <Click to see difference>

Argument(s) are different! Wanted:
bookRentalsRepository.save(
    bookrental.model.book.BookRentals@7574b32f
);
-> at bookrental.service.book.rentals.BookRentalServiceTest.rentBook(BookRentalServiceTest.java:48)
Actual invocation has different arguments:
bookRentalsRepository.save(
    bookrental.model.book.BookRentals@1c62c511
);
-> at bookrental.service.book.rentals.BookRentalService.rentBook(BookRentalService.java:36)

    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488)
    at bookrental.service.book.rentals.BookRentalServiceTest.rentBook(BookRentalServiceTest.java:48)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
pipilam
  • 587
  • 3
  • 9
  • 22
  • Hace you debugged your code? At which line does the NPE occur? From a first glance, it might be because your `when` method is commented therefore your mock doesn't return anything – Urosh T. Dec 15 '18 at 13:58
  • The same thing with uncommented `when`. While debugging program crashes at this line `bookRentalService.rentBook(user.getId(), book.getId());` – pipilam Dec 15 '18 at 14:01
  • 1
    It seems UserRepository is not initialised correctly through @InjectMocks. Please go through this link to fix the issue. https://stackoverflow.com/questions/41584454/spring-mockito-injectmocks-not-working – Amit Dec 15 '18 at 14:10
  • @Amit It's true. Thanks. – pipilam Dec 15 '18 at 14:11
  • For the second part of your question you are trying to save a BookRental object you have created but the `rentBook` method creates it own instance inside the method so it looks like you have to re-think this unit-test. Also, do not add a second question to an existing one like this, ask a new question instead. – Joakim Danielson Dec 15 '18 at 15:02

1 Answers1

3

From the stack trace, it seems it fails because you didn't mock also the user repository. Add the following:

@Mock
UserRepository userRepository;

...

when(userRepository.doesAccountExistsWithGivenID(user.getId()).thenReturn(true);
ctenescu
  • 104
  • 4
  • hey, that's true. Unfortunately another problem occured. I edited question. Could you check it out? There is a problem with verify. – pipilam Dec 15 '18 at 14:38
  • As already somebody pointed it out in an above comment, the issue is that you create a new instance of BookRentals in your code, therefore you cannot match it in your test. There are multiple ways of solving this. My personal preference is creating a BookRentalsFactory which will contain the code for creating a new instance of BookRentals, which then you can also inject and mock in your test. Also a side question, given you get the Book, why do you create a new Book and not use the found one? – ctenescu Dec 15 '18 at 21:39
  • I do not understand. I create once Book here `Book book = createDummyBook();` also I create once BookRentals here `BookRentals bookToRent = createDummyRentedBook(user, book);` Could you explain it? – pipilam Dec 16 '18 at 00:14
  • Yes, in your test you seem to do it right, but in your code you create a new instance of Book in prepareBookToRent(). Anyways, that was not the issue of your second error, the issue is that you cannot match a new instance of BookRentals with your test one. Besides the Factory solution proposed above, given you seem to handle simple POJOs, you could also implement equals() and hashCode() in your BookRentals and it's internals. – ctenescu Dec 16 '18 at 08:56
  • I create instance of BookRentals in prepareBookToRent(). I've made it as to make rentBook more transparent. Is that bad? About the question, I got `@EqualsAndHashCode` in BookRentals, should I implement it on my own? But I do not understand, if I comment line with `verify` test passes. I dont know why it cant pass with this line. Though, this line only verify if the save was run just once. – pipilam Dec 16 '18 at 09:58
  • Im not calling rentBook method, Im calling save method from repository. – pipilam Dec 16 '18 at 10:04
  • 1. It's not bad that you create an instance of BookRentals, my question was about the new instance of Book inside the call to BookRentals constructor, you could simply pass the book you found previously in bookToRent instance. I was just curious why you don't do it like that. 2. Do you have @EqualsAndHashCode also on Book and User and possibly any other inner fields of them? If you don't do the factory, you need to match it precisely by value on all fields. 3. If you just want to hack the verify, you could also put a matcher: verify(bookRentalsRepository, times(1)).save(any()); – ctenescu Dec 16 '18 at 11:46
  • 1. Could you give me a gist of how would you do that? 2. Ye, I've got this annotation in User, Book and BookRentals. 3. I think I'm forced to do this that way, because the '2' doesn't work. – pipilam Dec 16 '18 at 11:54
  • 1. I would change the signature to "private BookRentals prepareBookToRent(int userID, Book book)" and pass it directly to the constructor "return new BookRentals(book, new User(userID));". The call to it will become: BookRentals preparedBookToRent = prepareBookToRent(userID, bookToRent); 2. Maybe you could try also with your own equals/hashcode implementations, as maybe the annotation doesn't get processed in the unit test. – ctenescu Dec 16 '18 at 12:22
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185314/discussion-between-ctenescu-and-pipilam). – ctenescu Dec 16 '18 at 12:24