0

I have created a service layer which uses a DAO to get Couchbase Document objects and turn them in to POJO's I'd like to write a unit test for this method.

public List<Survey> findAll() throws CouchbaseLiteException {
    List<Document> docList = listsDAO.findAll();
    List<Survey> surveyList = new ArrayList<>();

    for(Document doc: docList) {
        ListClass listClass = listClassService.findById((String)doc.getProperty("listClass"));
        Survey survey = new Survey.Builder().withName((String)doc.getProperty("name"))
            .withListClass(listClass.getName()).build();
        surveyList.add(survey);
    }

    return surveyList;
}

I have attempted to use PowerMockito to do this

@RunWith(PowerMockRunner.class)
@PrepareForTest({Database.class})
public class ListsServiceTest {

    private Database database;

    @Mock
    private ListsDAO listsDAO;

    @Mock
    private ListClassService listClassService;

    private ListsService listsService;

    @Before
    public void setup() {
        database = PowerMockito.mock(Database.class);
        listsService = new ListsService(listsDAO, listClassService);
    }

    @Test
    public void testFindAll() {
        try {
            when(listsDAO.findAll()).thenReturn(TestUtils.createDocuments(database));
            when(listClassService.findById("listClass_1")).thenReturn(TestUtils.LIST_CLASS1);
            when(listClassService.findById("listClass_2")).thenReturn(TestUtils.LIST_CLASS2);

            List<Survey> surveyList = listsService.findAll();

        } catch (CouchbaseLiteException e) {
            e.printStackTrace();
        }

    }

}

But I'm having problems with the TestUtils.createDocuments() method

private static Document createDocument(Database database, Map<String, Object> docMap) {
    Document doc = database.createDocument();
    try {
        doc.putProperties(docMap);
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }
    return doc;
}

public static List<Document> createDocuments(Database database) {
    List<Document> docList = new ArrayList<>();
    docList.add(createDocument(database, DOC_MAP1));
    docList.add(createDocument(database, DOC_MAP2));
    return docList;
}

I get the following stack trace

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
-> at com.xxx.xxx.service.ListsServiceTest.testFindAll(ListsServiceTest.java:51)

E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, you naughty developer!
 3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed

    at com.xxx.xxx.util.TestUtils.createDocument(TestUtils.java:35)
    at com.xxx.xxx.util.TestUtils.createDocuments(TestUtils.java:46)
    at com.xxx.xxx.service.ListsServiceTest.testFindAll(ListsServiceTest.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:294)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:127)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:282)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:207)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:146)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:106)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)


Process finished with exit code -1

What am I doing wrong?

PDStat
  • 5,513
  • 10
  • 51
  • 86
  • I see no mocking for the `database.createDocument()` method call, that could be the issue, especially if its static. – Craig Sep 07 '15 at 09:38

2 Answers2

1

TestUtils.createDocuments tries to run method database.createDocument(), while in your case database is mocked object, and createDocument is not mocked.

I'd recommend not mocking database at all, and create a list of Documents manually (without ever calling database.createDocument()).

How about creating a set of Document mocks, and testing if same mocks are returned? For example:

Document doc1 = PowerMockito.mock(Document.class);
Document doc2 = PowerMockito.mock(Document.class);
when(listsDAO.findAll()).thenReturn(new Document[] {doc1, doc2});
...
bezmax
  • 25,562
  • 10
  • 53
  • 84
  • I have just tried this actually, the constructor for a Document object is `Document(Database database, String documentId)` so it still requires a Database object. – PDStat Sep 07 '15 at 09:36
  • I see what you're saying but the for loop relies on properties set in the Document object? – PDStat Sep 07 '15 at 09:45
  • I believe there is no other way. Just mock all of the fields you depend on in the Document object. Basically, Document is part of Couchbase API, so you either instantiate it or you mock it. Instantiating requires Database, which you can't do in the test. Therefore, the only remaining option is to fully mock the Document. – bezmax Sep 07 '15 at 09:54
  • 2
    Also, in my code, I try to avoid such problems by never returning classes from infrastructure level (in your case Document class) to the other layers of application. That is, I would have designed listsDAO to transform Document -> MyAppDocument and return it then. This way, you could've instantiated the MyAppDocument any way you like. – bezmax Sep 07 '15 at 09:56
  • OK I see, so that would essentially be a DTO? – PDStat Sep 07 '15 at 10:03
  • Well, DTO is a very broad term, but yes. – bezmax Sep 07 '15 at 10:22
1

Try adding a when statement for the database object, and return a Mock of the document and verify the method calls on it when your done.

// Add to your @Before method or in your test method
mockDocument = PowerMockito.mock(Document.class);
when(database.createDoccument()).thenReturn(mockDocument);

Afterwards you can verify that the correct parameters were passed to your mock

Mockito.verify(mockDocument).putProperties(docMap);

Hope that helps.

Craig
  • 2,286
  • 3
  • 24
  • 37