Исключение при тестировании LiveData - PullRequest
0 голосов
/ 29 октября 2019

Я пытаюсь протестировать функциональность класса, которая расширяет ViewModel, с JUnit5 и Mockito и получаю исключение java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. Я аннотировал тестовый класс с @ExtendWith(InstantExecutorExtension.class) , так как я тестирую LiveData, и значения должны быть непосредственнообновляется в вызывающем потоке, что можно сделать с помощью расширения в JUnit5.

Если я запускаю тесты по отдельности, они проходят, но когда я запускаю их все вместе, один проход и остальные все тесты не пройдены. Я аннотировал метод init () аннотацией @BeforeEach, чтобы у каждого теста был свежий экземпляр NoteViewModel, но я все еще сталкиваюсь с проблемой.

Следующий класс - это класс ViewModel, который отвечает за вставку и обновление заметок

public class NoteViewModel extends ViewModel {

private static final String TAG = "NoteViewModel";
public static final String NO_CONTENT_ERROR = "Can't save note with no content";

public enum ViewState {VIEW, EDIT}

private final NoteRepository noteRepository;

private MutableLiveData<Note> note  = new MutableLiveData<>();
private MutableLiveData<ViewState> viewState = new MutableLiveData<>();
private boolean isNewNote;

private Subscription updateSubscription, insertSubscription;


@Inject
public NoteViewModel(NoteRepository noteRepository) {
    this.noteRepository = noteRepository;
}

public LiveData<Resource<Integer>> insertNote() throws Exception{
    return LiveDataReactiveStreams.fromPublisher(
            noteRepository.insertNote(note.getValue())
                    .doOnSubscribe(new Consumer<Subscription>() {
                        @Override
                        public void accept(Subscription subscription) throws Exception {
                            insertSubscription = subscription;
                        }
                    })
    );
}

public LiveData<Resource<Integer>> updateNote() throws Exception{
    return LiveDataReactiveStreams.fromPublisher(
            noteRepository.updateNote(note.getValue())
                    .doOnSubscribe(new Consumer<Subscription>() {
                        @Override
                        public void accept(Subscription subscription) throws Exception {
                            updateSubscription = subscription;
                        }
                    })
    );
}

public LiveData<Note> observeNote(){
    return note;
}

public LiveData<ViewState> observeViewState(){
    return viewState;
}

public void setViewState(ViewState viewState){
    this.viewState.setValue(viewState);
}

public void setIsNewNote(boolean isNewNote){
    this.isNewNote = isNewNote;
}

public LiveData<Resource<Integer>> saveNote() throws Exception{

    if(!shouldAllowSave()){
        throw new Exception(NO_CONTENT_ERROR);
    }
    cancelPendingTransactions();

    return new NoteInsertUpdateHelper<Integer>(){

        @Override
        public void setNoteId(int noteId) {
            isNewNote = false;
            Note currentNote = note.getValue();
            currentNote.setId(noteId);
            note.setValue(currentNote);
        }

        @Override
        public LiveData<Resource<Integer>> getAction() throws Exception {
            if(isNewNote){
                return insertNote();
            }
            else{
                return updateNote();
            }
        }

        @Override
        public String defineAction() {
            if(isNewNote){
                return ACTION_INSERT;
            }
            else{
                return ACTION_UPDATE;
            }
        }

        @Override
        public void onTransactionComplete() {
            updateSubscription = null;
            insertSubscription = null;
        }
    }.getAsLiveData();
}

private void cancelPendingTransactions(){
    if(insertSubscription != null){
        cancelInsertTransaction();
    }
    if(updateSubscription != null){
        cancelUpdateTransaction();
    }
}

private void cancelUpdateTransaction(){
    updateSubscription.cancel();
    updateSubscription = null;
}

private void cancelInsertTransaction(){
    insertSubscription.cancel();
    insertSubscription = null;
}

private boolean shouldAllowSave() throws Exception{
    try{
        return removeWhiteSpace(note.getValue().getContent()).length() > 0;
    }catch (NullPointerException e){
        throw new Exception(NO_CONTENT_ERROR);
    }
}

public void updateNote(String title, String content) throws Exception{
    if(title == null || title.equals("")){
        throw new NullPointerException("Title can't be null");
    }
    String temp = removeWhiteSpace(content);
    if(temp.length() > 0){
        Note updatedNote = new Note(note.getValue());
        updatedNote.setTitle(title);
        updatedNote.setContent(content);
        updatedNote.setTimestamp(DateUtil.getCurrentTimeStamp());

        note.setValue(updatedNote);
    }
}

private String removeWhiteSpace(String string){
    string = string.replace("\n", "");
    string = string.replace(" ", "");
    return string;
}

public void setNote(Note note) throws Exception{
    if(note.getTitle() == null || note.getTitle().equals("")){
        throw new Exception(NOTE_TITLE_NULL);
    }
    this.note.setValue(note);
}

public boolean shouldNavigateBack(){
    if(viewState.getValue() == ViewState.VIEW){
        return true;
    }
    else{
        return false;
    }
} }

Ниже приведен класс тестирования

@ExtendWith(InstantExecutorExtension.class)
public class NoteViewModelTest {

// system under test
private NoteViewModel noteViewModel;

@Mock
private NoteRepository noteRepository;

@BeforeEach
public void init(){
    MockitoAnnotations.initMocks(this);
    noteViewModel = new NoteViewModel(noteRepository);
}

/*
    can't observe a note that hasn't been set
    In this test we skip the setNote() method and try to observe the note value
 */

@Test
public void cannotObserver_whenNoteNotSet() throws Exception
{
  // Arrange
    LiveDataTestUtil<Note> noteLiveData = new LiveDataTestUtil<>();

    // Act
    Note note = noteLiveData.getValue(noteViewModel.observeNote());

    // Assert
    assertNull(note);

}

/*Set a note and observe the value if it matches with the help of the result that we get from the Observer*/
@Test
public void observeNoteWhenSet() throws Exception
{
    // Arrange
    Note note =new Note(TestUtil.TEST_NOTE_1);
    LiveDataTestUtil<Note> liveDataTestUtil = new LiveDataTestUtil<>();

    // Act
    noteViewModel.setNote(note);
    Note noteFromObserver = liveDataTestUtil.getValue(noteViewModel.observeNote());

    // Assert that the value returned by the observer and the set value match
    assertEquals(note,noteFromObserver);
}

@Test
public void  insertNote_checkTheReturnedRow() throws Exception
{
  // Arrange
  Note note = new Note(TestUtil.TEST_NOTE_1);
  LiveDataTestUtil<Resource<Integer>> liveDataTestUtil = new LiveDataTestUtil<>();
  final int INSERTED_ROW = 1;
  Flowable<Resource<Integer>> observedNoteValue = SingleToFlowable.just(Resource.success(INSERTED_ROW,INSERT_SUCCESS));
  when(noteRepository.insertNote(any(Note.class))).thenReturn(observedNoteValue);
  // Act
  noteViewModel.setNote(note);
  noteViewModel.setIsNewNote(true);
  Resource<Integer> value = liveDataTestUtil.getValue(noteViewModel.insertNote());
  // Assert
  assertEquals(Resource.success(INSERTED_ROW,INSERT_SUCCESS),value);
}

// We expect the insertNote method not to be called when we are not attaching it to any Observer

@Test
public void noDatashouldBeInsertedWithoutAnObserver() throws Exception
{
    // Arrange
    Note note = new Note(TestUtil.TEST_NOTE_1);
    // Act (Here we don't attach any observable and just set the note)
    noteViewModel.setNote(note);
    verify(noteRepository,never()).insertNote(any(Note.class));
}

// throw an Exception when we null title for the note

@Test
public void throwExceptionForNullTitle() throws Exception
{
  final Note note = new Note(TestUtil.TEST_NOTE_1);
  note.setTitle(null);
 // Assert
    assertThrows(Exception.class, new Executable() {
        @Override
        public void execute() throws Throwable {
           noteViewModel.setNote(note);
        }
    });
}


@Test
public void updateNote_returnRow() throws Exception {
    // Arrange
    Note note = new Note(TestUtil.TEST_NOTE_1);
    LiveDataTestUtil<Resource<Integer>> liveDataTestUtil = new LiveDataTestUtil<>();
    final int updatedRow = 1;
    Flowable<Resource<Integer>> returnedData = SingleToFlowable.just(Resource.success(updatedRow, UPDATE_SUCCESS));
    when(noteRepository.updateNote(any(Note.class))).thenReturn(returnedData);

    // Act
    noteViewModel.setNote(note);
    noteViewModel.setIsNewNote(false);
    Resource<Integer> returnedValue = liveDataTestUtil.getValue(noteViewModel.saveNote());

    // Assert
    assertEquals(Resource.success(updatedRow, UPDATE_SUCCESS), returnedValue);
}

@Test
public void saveNote_shouldReturnFalseForNullContents() throws Exception
{
    // Arrange
    Note note = new Note(TestUtil.TEST_NOTE_1);
    note.setContent(null);
    // Act
    noteViewModel.setNote(note);
    noteViewModel.setIsNewNote(true);

    Exception exception = assertThrows(Exception.class, new Executable() {
        @Override
        public void execute() throws Throwable {
             noteViewModel.saveNote();
        }
    });
    assertEquals(NO_CONTENT_ERROR,exception.getMessage());
}
}

Класс расширения, который я 'm используя следующее

public class InstantExecutorExtension implements AfterEachCallback, BeforeAllCallback {
@Override
public void afterEach(ExtensionContext context) throws Exception {
    ArchTaskExecutor.getInstance().setDelegate(null);
}

@Override
public void beforeAll(ExtensionContext context) throws Exception {
   ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
       @Override
       public void executeOnDiskIO(@NonNull Runnable runnable) {
          runnable.run();
       }

       @Override
       public void postToMainThread(@NonNull Runnable runnable) {
           runnable.run();
       }

       @Override
       public boolean isMainThread() {
           return true;
       }
   });
}
}

Когда я запускаю тестовый класс, я получаю следующее исключение

java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.

at android.os.Looper.getMainLooper(Looper.java)
at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:77)
at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:116)
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:460)
at androidx.lifecycle.LiveData.setValue(LiveData.java:304)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at com.practice.unittesting.ui.note.NoteViewModel.setNote(NoteViewModel.java:207)
at com.practice.unittesting.ui.note.NoteViewModelTest.observeNoteWhenSet(NoteViewModelTest.java:88)
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:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at java.util.ArrayList.forEach(ArrayList.java:1251)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at java.util.ArrayList.forEach(ArrayList.java:1251)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
...