js6yaml
js6yaml

Reputation: 85

Getting NotAMockException on the below Test Case

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

Answers (2)

rieckpil
rieckpil

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

JavaMan
JavaMan

Reputation: 1217

I think the problem is the following line

@Mock @Autowired private FetchFileDetailsDAOImpl dao;

Try @Mock private FetchFileDetailsDAOImpl dao; instead

Upvotes: 0

Related Questions