Unit Test: Cannot use ArgumentCaptor - java

I am trying to create a unit test for the following method that calls another private method:
public CommandDTO create(final LabelRequest request) {
return saveLabel(new Label(), request);
}
private CommandDTO saveLabel(final Label label, final LabelRequest request) {
label.setName(request.getName());
final Label saved = labelRepository.saveAndFlush(label);
return CommandDTO.builder().uuid(saved.getUuid()).build();
}
It returns "nullpointer exception for the saved parameter as I did not mock in in my test.
Here is the Unit Test. I added 2 question as comment (Q1 and Q2). Could you please clarify me what is wrong?
#RunWith(MockitoJUnitRunner.class)
public class LabelServiceImplTest {
#Mock
private LabelRepository labelRepository;
#InjectMocks
private LabelServiceImpl labelService;
#Captor
ArgumentCaptor<Label> labelCaptor;
#Test
public void test_create {
// Q1: I am not sure if the following parts are needed
final LabelRequest request = new LabelRequest();
request.setName("Label Name");
final Label label = new Label();
label.setName(request.getName());
// Q2: I think there is no need to mock saveAndFlush method.
// But in this scene it returns "nullpointer exception"
when(labelRepository.saveAndFlush(any())).thenReturn(label);
CommandDTO result = labelService.create(request);
Mockito.verify(labelRepository).saveAndFlush(labelCaptor.capture());
final Label value = labelCaptor.getValue();
// then make necessary assertions
}
}

F I R S T
I think that you only declared your mocks but never defined/created them. There's basically three ways to do so:
1) mockito extension
The convenient way in JUnit 5 is to use the MockitoExtension:
#ExtendWith(MockitoExtension.class)
public class LabelServiceImplTest {
// declare mocks, spies and captors ...
// system under test
#InjectMocks
LabelService sut;
}
Nothing else should be needed now.
Mind that by #InjectMocks you instruct Mockito to create your system under test and inject the mocks. You may as well do that manually, e. g. by injecting mocks into the SUT's constructor. I prefer that way to have more control over the configuration of my SUT.
2) MockitoAnnotations.openMocks(testclass)
This method will inject mocks into the given object:
public class LabelServiceImplTest {
// declare mocks, spies and captors ...
// system under test
#InjectMocks
LabelService sut;
#BeforeEach
void setup() {
MockitoAnnotations.openMock(this)
// ...
}
}
On JUnit 4 you'd use MockitoAnnotations.initMocks(…) instead.
3) the old school way
public class LabelServiceImplTest {
LabelRepository labelRepositoryMock = Mockito.mock(LabelRepository.class);
// ...
// system under test
LabelServiceImpl sut;
#BeforeEach
void setup() {
sut = new LabelService(labelRepositoryMock);
// ...
}
}
S E C O N D
Your questions …
1) Do you need to create the request and the label?
Yes. You call the service using the request and the mock returns the label. I would suggest you do not use the request's property to set up your label, use a String literal instead.
2) Do you need to mock the saveAndFlush() method?
Yes. This is the part where you configure your SUT's environment to behave in a predictable way.
You might consider postfixing your mocks, spies and captors by: "Mock", "Spy" and "Captor".
You might also consider calling your system under test "sut".
#Mock
private LabelRepository labelRepositoryMock;
#Captor
ArgumentCaptor<Label> labelCaptor;
// system under test
#InjectMocks
private LabelServiceImpl sut;
This makes the test code more readably, I think.

Related

Mockito doNothing not working, void method is still executed [duplicate]

Preface:
This question and answer is intended as a canonical answer to
the majority of questions that arise due to misuse of Mockito or
misunderstanding how Mockito works and interacts with unit tests
written in the Java language.
I have implemented a class which should be unit tested. Note that the code shown here is only a dummy implementation and Random is for illustrative purposes. Real code would use a real dependency, such as another service or repository.
public class MyClass {
public String doWork() {
final Random random = new Random(); // the `Random` class will be mocked in the test
return Integer.toString(random.nextInt());
}
}
I want to use Mockito for mocking other classes and have written a really simple JUnit test. However, my class is not using the mock in the test:
public class MyTest {
#Test
public void test() {
Mockito.mock(Random.class);
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// this fails, because the `Random` mock is not used :(
}
}
Even running the test with the MockitoJUnitRunner (JUnit 4) or extending with MockitoExtension (JUnit 5) and annotating with #Mock does not help; the real implementation is still used:
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTest {
#Mock
private Random random;
#Test
public void test() {
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// `Random` mock is still not used :((
}
}
Why is the mocked class not used, even though the Mockito methods are called before my class is tested or the test is executed with the Mockito extension/runner?
Other variants of this question include but are not limited to:
My mocks return null / My stubs return null
NullPointerException when using Mockito
My mocks are null in test
My mocks don't return the expected value / My stubs do not return the expected value
Mockito thenReturn not honored / Mockito thenAnswer not honored
#InjectMocks not working
#Mock not working
Mockito.mock not working
My class is not using mocks / My class is not using stubs
My tests still call or execute the real implementation of a mocked/stubbed class
TLDR: Two or more distinct instances of your mock exist. Your test is using one instance and your class under test is using another instance. Or you are not using mocks at all in your class because you new up objects inside the class.
Problem Overview (Classes vs Instances)
Mocks are instances (that's why they are also called "mock objects"). Calling Mockito.mock on a class will return a mock object for this class. It must be assigned to a variable which can then be passed to the relevant methods or injected as dependency into other classes. It does not modify the class itself! Think about it: if that were true, then all instances of the class would somehow, magically, be converted to mocks. That would make it impossible to mock classes of which multiple instances are used or classes from the JDK, such as List or Map (which shouldn't be mocked in the first place, but that's a different story).
The same holds true for #Mock annotation with the Mockito extension/runner: a new instance of a mock object is created, which is then assigned to the field (or parameter) annotated with #Mock. This mock object still needs to be passed to the correct methods or injected as dependency.
Another way to avoid this confusion: new in Java will always allocate memory for an object and will initialize this new instance of the real class. It is impossible to override the behavior of new. Not even clever frameworks such as Mockito can do it.
Solution
»But how can I mock my class?« you will ask. Change the design of your classes to be testable! Every time you decide to use new, you commit yourself to an instance of this exact type. Multiple options exist, depending on your concrete use case and requirements, including but not limited to:
If you can change the signature/interface of the method, pass the (mock) instance as method parameter. This requires the instance to be available in all call sites, which might not always be feasible.
If you cannot change the signature of the method, inject the dependency in your constructor and store it in a field to be later used by methods.
Sometimes, the instance must only be created when the method is called and not prior to it. In that case, you can introduce another level of indirection and use something known as the abstract factory pattern. The factory object will then create and return the instance of your dependency. Multiple implementations of the factory can exist: one which returns the real dependency and another one which returns a test double, such as a mock.
Below you will find sample implementations for each of the options (with and without Mockito runner/extension):
Changing Method Signature
public class MyClass {
public String doWork(final Random random) {
return Integer.toString(random.nextInt());
}
}
public class MyTest {
#Test
public void test() {
final Random mockedRandom = Mockito.mock(Random.class);
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork(mockedRandom)); // JUnit 5
// Assert.assertEquals("0", obj.doWork(mockedRandom)); // JUnit 4
}
}
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
#Test
public void test() {
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork(random)); // JUnit 5
// Assert.assertEquals("0", obj.doWork(random)); // JUnit 4
}
}
Constructor Dependency Injection
public class MyClass {
private final Random random;
public MyClass(final Random random) {
this.random = random;
}
// optional: make it easy to create "production" instances (although I wouldn't recommend this)
public MyClass() {
this(new Random());
}
public String doWork() {
return Integer.toString(random.nextInt());
}
}
public class MyTest {
#Test
public void test() {
final Random mockedRandom = Mockito.mock(Random.class);
final MyClass obj = new MyClass(mockedRandom);
// or just obj = new MyClass(Mockito.mock(Random.class));
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
}
}
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
#Test
public void test() {
final MyClass obj = new MyClass(random);
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
}
}
Delayed Construction via Factory
Depending on the number of constructor arguments of your dependency and need for expressive code, one could use existing interfaces from the JDK (Supplier, Function, BiFunction) or introduce a custom factory interface (annotated with #FunctionInterface if it only has a single method).
The following code will opt for a custom interface, but would work just fine with Supplier<Random>.
#FunctionalInterface
public interface RandomFactory {
Random newRandom();
}
public class MyClass {
private final RandomFactory randomFactory;
public MyClass(final RandomFactory randomFactory) {
this.randomFactory = randomFactory;
}
// optional: make it easy to create "production" instances (again: I wouldn't recommend this)
public MyClass() {
this(Random::new);
}
public String doWork() {
return Integer.toString(randomFactory.newRandom().nextInt());
}
}
public class MyTest {
#Test
public void test() {
final RandomFactory randomFactory = () -> Mockito.mock(Random.class);
final MyClass obj = new MyClass(randomFactory);
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
}
}
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private RandomFactory randomFactory;
#Test
public void test() {
// this is really awkward; it is usually simpler to use a lambda and create the mock manually
Mockito.when(randomFactory.newRandom()).thenAnswer(a -> Mockito.mock(Random.class));
final MyClass obj = new MyClass(randomFactory);
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
}
}
Corollary: (Mis-)using #InjectMocks
But I'm using #InjectMocks and verified with the debugger that I have mocks inside my class under test. Yet, the mock methods I set up with Mockito.mock and Mockito.when are never called! (In other words: "I get an NPE", "my collections are empty", "default values are returned", etc.)
— a confused developer, ca. 2022
Expressed in code, the above quote would look something like this:
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
#InjectMocks
private MyClass obj;
#Test
public void test() {
random = Mockito.mock(Random.class);
Mockito.when(random.nextInt()).thenReturn(42);
Assertions.assertEquals("42", obj.doWork()); // JUnit 5
// Assert.assertEquals("42", obj.doWork()); // JUnit 4
}
}
The problem with the code above is the first line in the test() method: it creates and assigns a new mock instance to the field, effectively overwriting the existing value. But #InjectMocks injects the original value into the class under test (obj). The instance created with Mockito.mock only exists in the test, not in the classes under test.
The order of operations here is:
All #Mock-annotated fields get assigned a new mock object.
The #InjectMocks-annotated field gets injected references to the mock object(s) from step 1.
The reference in the test class is overwritten with a different reference to the new mock object (created via Mockito.mock). The original reference is lost and no longer available in the test class.
The class under test (obj) still holds a reference to the initial mock instance and uses that. The test only has a reference to the new mock instance.
This basically boils down to Is Java "pass-by-reference" or "pass-by-value"?.
You can verify this with a debugger. Set a breakpoint and then compare the object addresses/ids of the mock fields in the test class and in the class under test. You will notice that those are two different, unrelated object instance.
The solution? Don't overwrite the reference, but set up the mock instance created via the annotation. Simply get rid of the re-assignment with Mockito.mock:
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
#InjectMocks
private MyClass obj;
#Test
public void test() {
// this.random must not be re-assigned!
Mockito.when(random.nextInt()).thenReturn(42);
Assertions.assertEquals("42", obj.doWork()); // JUnit 5
// Assert.assertEquals("42", obj.doWork()); // JUnit 4
}
}
Corollary: Object life cycles and magic framework annotations
I followed your advice and use dependency injection to manually pass the mock into my service. It's still not working and my test fails with null pointer exceptions, sometimes even before a single test method actually runs. You lied, bruh!
— another confused developer, late 2022
The code would likely look something like:
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
private final MyClass obj = new MyClass(random);
#Test
public void test() {
Mockito.when(random.nextInt()).thenReturn(42);
Assertions.assertEquals("42", obj.doWork()); // JUnit 5
// Assert.assertEquals("42", obj.doWork()); // JUnit 4
}
}
This is very similar to the first corollary and boils down to object life cycles and references vs values. The steps happening in the code above are as follows:
A new instance of MyTestAnnotated is created by the testing framework (e.g. new MyTestAnnotated()).
All constructors and field initializers are executed. There are no constructors here, but a field initializer: private MyClass obj = new MyClass(random);. At this point in time, the random field still has its default value null → the obj field is assigned new MyClass(null).
All #Mock-annotated fields get assigned a new mock object. This does not update the value in MyService obj, because it got passed null initially, not a reference to the mock.
Depending on your MyService implementation, this might already fail when creating an instance of the test class (MyService might perform parameter validation of its dependencies in the constructor); or it might only fail when executing a test method (because the dependency is null).
The solution? Familiarize yourself with object life cycles, field initializer order, and the point in time when mock frameworks can/will inject their mocks and update references (and which references are updated). Try to avoid mixing "magic" framework annotations with manual setup. Either create everything manually (mocks, service), or move the initialization to the methods annotated with #Before (JUnit 4) or #BeforeEach (JUnit 5).
#ExtendWith(MockitoExtension.class) // JUnit 5
// #RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTestAnnotated {
#Mock
private Random random;
private MyClass obj;
#BeforeEach // JUnit 5
// #Before // JUnit 4
public void setup() {
obj = new MyClass(random);
}
#Test
public void test() {
Mockito.when(random.nextInt()).thenReturn(42);
Assertions.assertEquals("42", obj.doWork()); // JUnit 5
// Assert.assertEquals("42", obj.doWork()); // JUnit 4
}
}
Alternatively, set up everything manually without annotations which require a custom runner/extension:
public class MyTest {
private Random random;
private MyClass obj;
#BeforeEach // JUnit 5
// #Before // JUnit 4
public void setup() {
random = Mockito.mock(random);
obj = new MyClass(random);
}
#Test
public void test() {
Mockito.when(random.nextInt()).thenReturn(42);
Assertions.assertEquals("42", obj.doWork()); // JUnit 5
// Assert.assertEquals("42", obj.doWork()); // JUnit 4
}
}
References
Is Java "pass-by-reference" or "pass-by-value"?
Initialising mock objects - Mockito
Difference between #Mock and #InjectMocks

Mockito Junit on a reference object

I am facing a strange problem while trying to unit test my code.
Here is my code :
public class ItemService {
private OfferService offerService;
#Inject
public ItemService (OfferService offerService){
this.offerService = offerService;
}
public List<Item> buildItems(ItemInfo itemInfo) {
List<Item> items = processItem(itemInfo);
Offers<Offer> offers = fetchItemInfo(items);
// based on the object offers, do some processing
}
private Offers<Offer> fetchItemInfo(List<Item> items) {
Offers<Offer> offers = new Offers<>();
// some processing here with offers.
// calling the db to fetch details
offerService.fetchInfoFromDB(offers);
return offers;
}
}
public class OfferService {
public void fetchInfoFromDB(Offers<Offer> offers) {
// fetching details from DB
// and updating the object **offers**
myDao.getDetailsById(id);
}
}
Now I have written junit to test the method buildItems()
UPDATE updating the mocks used and mock injection.
#RunWith(PowerMockRunner.class)
#PrepareForTest(ItemService.class)
public class ItemServiceTest{
#Mock private MyDAO myDao;
#Mock private OfferService offerService;
#Before
public void setUp() throws Exception {
ItemService itemService = new ItemService (offerService, myDao);
}
public void testBuildItems(){
// some code -----
itemInfo = buildItemInfo();
offerDetail = buildOfferDetail();
when(myDao.getDetailsById(Mockito.anyLong())).thenReturn(offerDetail);
// some code -----
// I need to implement some code which will actually call
// offerService.fetchInfoFromDB(offers);
// and update the --offers-- object and return it.
List<Item> items = itemService.buildItems(itemInfo);
Assert.assertNotNull(items);
}
}
I am running with coverage and I can see that the below line got executed but the actual method is not getting called :
offerService.fetchInfoFromDB(offers);
I am getting null values in offers. Then I added the below line :
doCallRealMethod().when(offerService).fetchInfoFromDB(offers);
Still the same result. The offers object is passed by reference and is getting updated after the DB call which I am mocking already. But upto that call my code is not reaching. How can I update the offers object in my junit. Please help.
Your test is calling a zero arg ItemService() constructor, not the one arg #Inject constructor you posted. Either your code won't compile, or you haven't actually shown us the code in question.
Also, you say you are mocking offerService:
You call when on myDao and not offerService,
you do not pass your mock offerService into your ItemService constructor, as in new ItemService(offerService), and
your doCallRealMethod won't work because your mock offerService won't use your mock myDao; you'll need to mock the call on offerService directly with a thenAnswer that changes the passed List<Offer>, as on my question you linked.
doAnswer(invocation -> {
((List<Offer>) invocation.getArgument(0)).add(offerDetail);
return null;
}).when(offerService).fetchInfoFromDb(any());
If you fix those three you will be considerably closer to a working test.

PowerMock static method mocking isn't taken into account for each #InjectMocks invocation

I want to write some unit tests, that use JUnit 4.12, Mockito 1.9.5 and PowerMock 1.6.1.
The class has some fields annotated with #Mock, as well as some fields annotated with #InjectMocks.
The attribute that is annotated with #InjectMocks reaches at some point a parent constructor which contains some static method invokation, that should be mocked with PowerMock.
The problem is the first test works seamlessly, while the second test does not seem to mock the static methods at all.
#RunWith(PowerMockRunner.class)
#PrepareForTest({ StaticClass.class })
public class TestClass {
#Mock
private SomeClass attribute1;
#InjectMocks
private SomeOtherClass attribute2;
#BeforeClass
public static void setUp() {
PowerMockito.mockStatic(StaticClass.class);
when(StaticClass.staticMethod(any(), any()).thenReturn(new SomeConcreteClass());
}
#Test
public void test1() {
assertEquals(attribute2.method1(), value1);
}
#Test
public void test2() {
assertEquals(attribute2.method2(), value2);
}
}
public class SomeOtherClass {
private SomeClass attribute;
public SomeOtherClass() {
SomeConcreteClass value = StaticClass.staticMethod(argument1, argument2);
value.someOtherMethod();
}
}
As mentioned before, the first test passes and the StaticClass.staticMethod() is mocked as expected by PowerMock.
The second test does not pass and it throws a NullPointerException at line when someOtherMethod is called on value (because value = null, as the StaticClass.staticMethod was not mocked anymore by PowerMock).
As explained in (Mocking behaviour resets after each test with PowerMock) Powermock resets the mocks before each test.
For some reason it works the first time - there exists
an unresolved bug report for that issue (https://github.com/powermock/powermock/issues/398).
Its arguably bad design, but a way to do what you want is the following:
Instead of relying on the annotation set up the mocks manually.
private SomeClass attribute;
private SomeOtherClass testClass;
#Before
public void setUp() {
PowerMockito.mockStatic(StaticClass.class);
Mockito.when(StaticClass.staticMethod(anyString())).thenReturn(new SomeConcreteClass());
attribute = Mockito.mock(SomeClass.class);
testClass = new SomeOtherClass();
// assign mock manually
testClass.attribute = attribute;
}
The prefered way would be to supply the attribute using the constructor of SomeOtherClass, however since you seem to use a empty constructor
you will have to set the value from the outside. If the attribute instance is not accessible you might be forced to use reflections.
The cleaner way would be to refactor the constructor of you SomeOtherClass to not use a static method inside. Instead passing SomeConcreteClass as a parameter to the constructor is the way to go.
Some people even say you should not have any logic inside of a constructor.

Mockito verify unit test - Wanted but not invoked. Actually, there were zero interactions with this mock

At first I want to sorry for my english.
I started to make some unit tests (i've never done this before, i'm a new guy in programming).
I have to test simple adding product to database (DynamoDB) method using mockito.verify but I have
"Wanted but not invoked. Actually, there were zero interactions with this mock."
Error and I don't know what to do.
This is my method code (in KitchenService class):
public Product addProduct(Product content) {
ObjectMapper objectMapper = new ObjectMapper();
String mediaJSON = null;
String authorJSON = null;
String productKindsJSON = null;
try {
mediaJSON = objectMapper.writeValueAsString(content.getMedia());
authorJSON = objectMapper.writeValueAsString(content.getAuthor());
productKindsJSON = objectMapper.writeValueAsString(content.getProductKinds());
} catch (JsonProcessingException e) {
logger.log(e.getMessage());
}
Item item = new Item()
.withPrimaryKey("id", UUID.randomUUID().toString())
.with("name", content.getName())
.with("calories", content.getCalories())
.with("fat", content.getFat())
.with("carbo", content.getCarbo())
.with("protein", content.getProtein())
.with("productKinds", productKindsJSON)
.with("author", authorJSON)
.with("media", mediaJSON)
.with("approved", content.getApproved());
Item save = databaseController.saveProduct(PRODUCT_TABLE, item);
logger.log(save + " created");
return content;
}
And this is test code:
#Test
public void addProduct() throws Exception {
KitchenService instance = mock(KitchenService.class);
Product expectedProduct = new Product();
expectedProduct.setName("kaszanka");
expectedProduct.setCalories(1000);
expectedProduct.setFat(40.00);
expectedProduct.setCarbo(20.00);
expectedProduct.setProtein(40.00);
expectedProduct.setProductKinds(Collections.singletonList(ProductKind.MEAT));
expectedProduct.setApproved(false);
Author expectedAuthor = new Author();
expectedAuthor.setId("testID");
expectedAuthor.setName("Endrju Golota");
expectedProduct.setAuthor(expectedAuthor);
Media expectedMedia = new Media();
expectedMedia.setMediaType(MediaType.IMAGE);
expectedMedia.setName("dupajasia");
expectedMedia.setUrl("http://blabla.pl");
expectedProduct.setMedia(expectedMedia);
verify(instance, times(1)).addProduct(expectedProduct);
}
This is what I got after test:
Wanted but not invoked:
kitchenService.addProduct(
model.kitchen.Product#a0136253
);
-> at service.kitchen.KitchenServiceTest.addProduct(KitchenServiceTest.java:80)
Actually, there were zero interactions with this mock.
Can someone tell me what im doing wrong?
What you should mock and verify is the databaseController dependency:
#Test
public void addProduct() throws Exception {
KitchenService instance = new KitchenService(); // you should create the class under test
DatabaseController controllerMock = mock(DatabaseController.class); // mock the controller
instance.setController(controller); // inject the mock
...
// Act
instance.addProduct(expectedProduct);
// Assert
verify(controller).saveProduct(Mockito.eq(PRODUCT_TABLE), Mockito.any(Item.class));
}
You should verify that the database is called within the service.. checking that it was invoked with any Item object should be enough.
Mocking is a tool that you only use for dependencies of the class that is being tested.
It appears that your test does not care about the Author, Media, and Product objects,
these are just dependencies of the method you want to test;
mock them.
Organization will greatly help your test;
do something like this:
public class TestKitchenService
{
private static String VALUE_PRODUCT_NAME = "VALUE_PRODUCT_NAME";
... use constants for other values as well. The value of the constant does not matter.
#InjectMocks
private KitchenService classToTest;
private InOrder inOrder;
#Mock
private Author mockAuthor;
#Mock
private DatabaseController mockDatabaseController;
#Mock
private Logger mockLogger;
#Mock
private Media mockMedia;
#Mock
private Product mockProduct;
#After
public void afterTest()
{
inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(mockAuthor);
verifyNoMoreInteractions(mockDatabaseController);
verifyNoMoreInteractions(mockLogger);
verifyNoMoreInteractions(mockMedia);
verifyNoMoreInteractions(mockProduct);
}
#Before
public void beforeTest()
{
MockitoAnnotations.initMocks(this);
doReturn(mockAuthor).when(mockProduct).getAuthor();
doReturn(mockMedia).when(mockProduct).getMedia();
doReturn(VALUE_PRODUCT_NAME).when(mockProduct).getName();
doReturn(Collections.singletonList(ProductKind.MEAT)).when(mockProduct).getProductKinds();
... doReturns for the other product values.
inOrder = inOrder(
mockAuthor,
mockDatabaseController,
mockLogger,
mockMedia,
mockProduct);
ReflectionTestUtils.setField(
classToTest,
"databaseController",
mockDatabaseController);
ReflectionTestUtils.setField(
classToTest,
"logger",
mockLogger);
}
#Test
public void addProduct_success()
{
final Product actualResult;
actualResult = classToTest.addProduct(mockProduct);
assertEquals(
mockProduct,
actualResult);
inOrder.verify(mockProduct).getMedia();
inOrder.verify(mockProduct).getAuthor();
inOrder.verify(mockProduct).getProductKinds();
inOrder.verify(mockProduct).getName();
... inOrder.verify for the other product values.
inOrder.verify(mockDatabaseController).saveProduct(
eq(PRODUCT_TABLE),
any(Item.class));
}
}
The only things that should be mocked -- if anything -- are the ObjectMapper and databaseController. One only mocks collaborator objects, and almost never the system/class under test (very rare cases exist for "spying" on the SUT). And depending on what the ObjectMapper is and how transparent it's operation is, you may not really want to even mock that. Furthermore, as your implementation code is written instantiating the ObjectMapper by directly calling a constructor, you can't even mock it.
While I love the use of Mockito and mock objects, sometimes it is worthwhile to simply test with as many real objects as possible. This is especially true when your collaborators are simple, straightforward, have no side effects, and don't require complex initialization or setup. Only use mocks when it simplifies the test setup or verification.

In a JUnit test is there a rule to set the subject of the test

I've been writing a lot of JUnit tests lately and see this same boilerplate pattern.
public class MathOpTest {
private MathOp a;
#Before
public void setUp(){
a = new MathOp();
}
...
}
Is there an annotation to set this up for me as I always need to write a setUp method and it usually only has the single class I'm testing.
Something like:
public class MathOpTest {
#TestSubject
private MathOp a;
...
}
You can assign the fields when they are declared:
public class MathOpTest {
private final MathOp mathOp = new MathOp();
...
}
This is simple and straight-forward, so I recommend that you assign fields in your test class at declaration time whenever possible (certainly in the case you gave).
If you want to understand a bit more, read on.
JUnit will create a unique instance of your test class for each test method, so even if your test modifies internal state of MathOp, using fields this way is safe as long as your tests don't modify global state.
For JUnit4-style tests (i.e. tests that do not extend junit.framework.TestCase) JUnit will create the test class just before the test method is run, and make it eligible for garbage collection after the test method completes.
Use #Before methods for more complex initialization.
Usually I use #Before when:
Initialization of the field is complex
Initialization of the field requires calling code that is declared to throw a checked exception
You need to do initialization after a #Rule has been applied (for instance, injecting a mock into a constructor)
Usually you would create the object under test in the test method when the class needs to be constructed different ways for different use cases.
Examples
Here is an example of using #Before and initMocks():
public class MathOpTest {
#Mock private Calculator mockCalculator;
#Mock private Supplier<Double> mockPreviousResultSupplier;
private MathOp mathOp;
#Before
public void createMathOp() {
MockitoAnnotations.initMocks(this);
mathOp = new MathOp(
mockCalculator, mockPreviousResultSupplier);
}
...
}
Here's an example of a #Before method that uses the result of a #Rule:
public class MyWriterTest {
#Rule public final TemporaryFolder folder = new TemporaryFolder();
private File output;
private MyWriter writer;
#Before
public void createMyWriter() {
output = folder.newFile();
writer = new MyWriter(output);
}
...
}
Aside: I personally wouldn't recommend using #InjectMocks to create the class you are testing. It's too much magic for my taste. Having an explicit constructor is cleaner and simpler, and I like my tests to be clear and simple :-)
Nothing like this directly exists in vanilla JUnit to my recollection. Most people elect to either initialize their test subject in a #Before statement, or inside of their tests. In its defense, it makes it clear what is being established before the tests are run, and it always resets the state of your test object.
If you're using Mockito, you actually do have the benefits of declaring a class and annotating it with #InjectMocks to both instantiate the class and inject whatever #Mock classes you had prior.

Categories

Resources