I am familiar with SpringBoot and I have written a small app. It has a ProductRepository
extending CrudRepository
. My ProductService
is this:
public interface ProductService {
Iterable<Product> listAllProducts();
Product getProductById(Integer id);
Product saveProduct(Product product);
void deleteProduct(Integer id);
}
The ProductServiceImpl
class auto wires in ProductRepository
and provides implementation using ProductRepository
.
My app is running as expected, and this is how I am testing the repository.
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {RepositoryConfiguration.class})
public class ProductRepositoryTest {
private ProductRepository productRepository;
@Autowired
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Test
public void testSaveProduct(){
//setup product
Product product = new Product();
product.setDescription("Shirt");
product.setPrice(new BigDecimal("18.95"));
product.setProductId("1234");
assertNull(product.getId()); //null before save
productRepository.save(product);
assertNotNull(product.getId()); //not null after save
//fetch from DB
Product fetchedProduct = productRepository.findOne(product.getId());
//should not be null
assertNotNull(fetchedProduct);
}
}
I want to know how I can unit test ProductRepository
without external dependency (which the test above have and so don't qualify as a unit test. I believe it's more of an integration test).
Also, how I can unit test my ProductService
? I tried mocking ProductRepository
and Product
with Mockito, like this:
public class ProductServiceImplTest {
private ProductServiceImpl productServiceImpl;
private ProductRepository productRepository;
private Product product;
@Before
public void setupMock() {
MockitoAnnotations.initMocks(this);
productServiceImpl=new ProductServiceImpl();
productRepository = mock(ProductRepository.class);
product = mock(Product.class);
}
@Test
public void testRetrieveById() throws Exception {
when(productRepository.findOne(5)).thenReturn(product);
assertEquals(product, productServiceImpl.getProductById(5));
}
}
But this is what I get:
java.lang.NullPointerException
at
ProductServiceImpl.getProductById(ProductServiceImpl.java:24)
at ProductServiceImplTest.testRetrieveById(ProductServiceImplTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
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.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
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.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:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Am I using the Mockito.mock()
correctly? Also what will be the difference if I use Mockito.spy()
instead? Any help will be highly appreciated.
If you want to write unit tests then remove all those unnecessary annotations that are used for intergration testing.
Also you need to initiate the mocks in the @Before
method:
public class ProductServiceImplTest {
@InjectMocks
private ProductServiceImpl productServiceImpl;
@Mock
private ProductRepository productRepository;
@Mock
private Product product;
@Before
public void setupMock() {
MockitoAnnotations.initMocks(this);
}
I think in your case, using mock
should be enough.
The only time that you should use a spy
is to invoke it on the class that you are testing which in your case would be:
productServiceImpl=new ProductServiceImpl();
prodServiceSpy = spy(productServiceImpl);
and mock some of the methods which implementation you do not want to be invoked, or invoke with a custom implementation required by the test scenario.