Я пытаюсь протестировать функциональность класса, которая расширяет 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)