Reputation: 85
I have tried to run the below test and am facing NotAMock
Exception and not sure how to resolve it. I have been trying to read the concept that methods of class under test cannot be mocked but I am unable to come clear on the subject. If someone could explain me the Why on my own example, I am hopeful of understanding it better.
I tried various ways of changing @RunWith
runners for Unit or Integration test setup or using @Spy
instead of @Mock
or not have @Autowired
etc but either was facing dao Null Pointer or Not a Mock Exception variably.
Am I supposed to Use another class and inject the Listener
in that class and mock the listener to achieve the functionality of being able to mock the methods and capture the arguments dynamically. Will this work because it is no more the class under test and therefore the methods could be mocked? If so, how is this realized. If not, what is the right way. My sense is moving the listener to another class will only extend my current set of issues of not being able to mock but does not resolve it. However, I am not sure what is the right outcome.
@Component
public class FileEventListener implements ApplicationListener<FileEvent> {
@Autowired private FetchFileDetailsDAO fileDao;//Dao is annotated with @Transactional
@Override
public void onApplicationEvent(FileEvent event) {
fileDao.getDetailsForFile(event.fileName())
}
}
-----------------------------------------------------------------------------------------
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest(classes = TestApp.class)
@RunWith(SpringRunner.class)
public class TestClass {
@Captor private ArgumentCaptor<Object> captor;
@Mock @Autowired private FetchFileDetailsDAO dao;
@InjectMocks @Autowired private FileEventListener listener;
@Before
public void setup() throws IOException {
MockitoAnnotations.initMocks(this);
}
@Test
@Transactional
@Rollback(true)
public void test() throws Exception {
FileEvent ev = new FileEvent();
...
listener.onApplicationEvent(ev);
verify(dao, times(1)).getDetailsForFile((String)captor.capture())
}
Upvotes: 2
Views: 853
Reputation: 12021
You are mixing things up here. There is an important difference between @Mock
and @MockBean
.
You use the first annotation if you want to write a unit test without any Spring Context support (speak @SpringBootTest
, @DataJpaTest
, etc.). For such tests, you can use @Mock
and @InjectMocks
.
As you are writing an integration test (you are starting the whole context with @SpringBootTest
), you work with managed Spring beans inside your test. Hence you are not writing a unit test anymore.
If you want to replace a Spring bean with a mocked version of it inside your Spring Test Context, you have to use @MockBean
:
@SpringBootTest(classes = TestApp.class)
@RunWith(SpringRunner.class)
@RunWith(MockitoJUnitRunner.class) // will do the Captor initialization for you
public class TestClass {
@Captor
private ArgumentCaptor<Object> captor;
@MockBean
private FetchFileDetailsDAO dao;
@Autowired
private FileEventListener listener;
@Test
@Transactional
@Rollback(true)
public void test() throws Exception {
FileEvent ev = new FileEvent();
// ...
listener.onApplicationEvent(ev);
verify(dao, times(1)).getDetailsForFile((String)captor.capture())
}
Starting the whole context however for this test is IMHO overkill. You are better off writing a good old unit test with just JUnit and Mockito.
In addition to this, also I would rethink what benefit your current tests adds to your project as it is literally duplicating the business logic. Maybe there is more code that is not present here.
You can find a more detailed summary for the difference between @Mock and @MockBean in this article.
Upvotes: 3
Reputation: 1217
I think the problem is the following line
@Mock @Autowired private FetchFileDetailsDAOImpl dao;
Try @Mock private FetchFileDetailsDAOImpl dao;
instead
Upvotes: 0