मैं हाल ही में एंड्रॉइड यूनिट परीक्षण में कूद गया और मैं अभी भी अपने यूनिट परीक्षण लिखने में संघर्ष कर रहा हूं। मैं अपने प्रस्तुतकर्ता का परीक्षण करने की कोशिश कर रहा हूं, विशेष रूप से एक विधि जो जीथब एपी से भंडारों की एक सूची देता है, लेकिन मुझे एक नल पॉइंटर अपवाद मिलता रहता है और मुझे समझ में नहीं आता क्यों।

RepositoriesPresenter विधि मैं इकाई परीक्षण करना चाहता हूँ:

public void presenterLoadRepos(boolean onlineRequired, String owner) {
    // Clear old data on view
    view.clearRepos();

    //recovering access token data from Shared Preferences
    String accessTokenString = repository.getAccessTokenString();
    String accessTokenTypeString = repository.getAccessTokenType();

    if(onlineRequired){
        Disposable disposable = repository.loadRemoteRepos(owner, accessTokenString, accessTokenTypeString, PER_PAGE_VALUE)  //this is line 188
                .subscribeOn(ioScheduler) //this is line 189
                .observeOn(uiScheduler)
                .subscribe(this::handleReturnedData, this::handleError, () -> view.stopLoadingIndicator());
        disposeBag.add(disposable);
    }else {
        Disposable disposable = repository.loadLocalRepos(owner, accessTokenString, accessTokenTypeString, PER_PAGE_VALUE)
                .subscribeOn(ioScheduler)
                .observeOn(uiScheduler)
                .subscribe(this::handleReturnedData, this::handleError, () -> view.stopLoadingIndicator());
        disposeBag.add(disposable);
    }

}

संपूर्ण रिपॉजिटरी प्रस्तुतकर्ता वर्ग:

public class RepositoriesPresenter implements RepositoriesContract.Presenter, LifecycleObserver {

private static final String TAG = RepositoriesPresenter.class.getSimpleName();


private GitHubChallengeRepository repository;

private RepositoriesContract.View view;

private Scheduler ioScheduler;
private Scheduler uiScheduler;

private CompositeDisposable disposeBag;

@Inject
public RepositoriesPresenter(GitHubChallengeRepository repository, RepositoriesContract.View view,
                             @RunOn(IO) Scheduler ioScheduler, @RunOn(UI) Scheduler uiScheduler) {
    this.repository = repository;
    this.view = view;
    this.ioScheduler = ioScheduler;
    this.uiScheduler = uiScheduler;

    // Initialize this presenter as a lifecycle-aware when a view is a lifecycle owner.
    if (view instanceof LifecycleOwner) {
        ((LifecycleOwner) view).getLifecycle().addObserver(this);
    }

    disposeBag = new CompositeDisposable();
}

@Override @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onAttach() {
    presenterLoadRepos(false, view.getOwner());
}

@Override @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onDetach() {
    // Clean up any no-longer-use resources here
    disposeBag.clear();
}


@Override
public void checkRepoPerUser(String owner) {

    //recovering access token data from Shared Preferences;
    String accessTokenString = repository.getAccessTokenString();
    String accessTokenTypeString = repository.getAccessTokenType();

    //Asking for a list of repositories with 1 repository per page.
    //This let us know how many repositories we found and also to deal with error response code
    Disposable disposable = repository.checkReposPerUser(owner, accessTokenString, accessTokenTypeString, "1")
            .subscribeOn(ioScheduler)
            .observeOn(uiScheduler)
            .subscribe(this::handleReturnedHeaderData, this::handleHeaderError);
    disposeBag.add(disposable);
}

@VisibleForTesting
private void handleReturnedHeaderData(Response<List<Headers>> response) {
    //getting value 'Link' from response headers in order to count the repositories
    String link = response.headers().get("Link");
    String message = response.message();

    //checking GitHub API requests limit
    String limit = response.headers().get("X-RateLimit-Limit");
    Log.d(TAG, "Limit requests: " + limit);
    String limitRemaining = response.headers().get("X-RateLimit-Remaining");
    Log.d(TAG, "Limit requests remaining: " + limitRemaining);

    //getting http response code
    int code = response.code();

    switch (code){
        case 404:
            if(message.equalsIgnoreCase("not found")){ //User not exists
                view.showUserNotFoundMessage();
            }else{
                view.showErrorMessage(message);
            }
            break;
        case 403:
            //GitHub API requests limit reached
            //Instead of showing an error, we start the login process,
            // store another access token in shared Preferences and resend the same request that failed before
            view.startLogin();
            break;
        case 200:
            if(link == null){ //Link value is not present into the header, it means there's 0 or 1 repo
                Log.d(TAG, "Total repos for current user is 0 or 1.");
                //get the repository
                searchRepo(view.getOwner()); //Starting looking for data
            }else if( link != null){
                //get last page number: considering that we requested all the repos paginated with
                //only 1 repo per page, the last page number is equal to the total number of repos
                String totalRepoString = link.substring(link.lastIndexOf("&page=") + 6, link.lastIndexOf(">"));
                Log.d(TAG, "Total repos for current user are " + totalRepoString);

                // TODO once we know how many repositories we have, we can decide how many calls to do (total repositories/100 rounded up )

                //get the repositories
                searchRepo(view.getOwner()); //Starting 3 looking for data
            }
            break;
        default:
            searchRepo(view.getOwner()); //Starting 3 looking for data
            break;
    }
}

private void handleHeaderError(Throwable error) {
    Log.e(TAG, error.getMessage(), error);
    view.showErrorMessage(error.getLocalizedMessage());
}

@Override public void searchRepo(final String owner) {

    view.showProgressBarIfHidden();

    //recovering access token data from Shared Preferences
    String accessTokenString = repository.getAccessTokenString();
    String accessTokenTypeString = repository.getAccessTokenType();

    // Load new one and populate it into view
    Disposable disposable = repository.loadRemoteRepos(owner, accessTokenString, accessTokenTypeString, "100")
            .flatMap(Observable::fromIterable)
            .filter(repo -> repo.getName() != null)
            .toList()
            .toObservable()
            .subscribeOn(ioScheduler)
            .observeOn(uiScheduler)
            .subscribe(repos -> {
                if (repos.isEmpty()) {
                    // Clear old data from recycler view
                    view.clearRepos();
                    // Show notification
                    view.showEmptySearchResult();
                } else {
                    // Update recycler view items
                    view.showRepos(repos);

                }
            });

    disposeBag.add(disposable);

}

public void presenterLoadRepos(boolean onlineRequired, String owner) {
    // Clear old data on view
    view.clearRepos();

    //recovering access token data from Shared Preferences
    String accessTokenString = repository.getAccessTokenString();
    String accessTokenTypeString = repository.getAccessTokenType();

    if(onlineRequired){
        Disposable disposable = repository.loadRemoteRepos(owner, accessTokenString, accessTokenTypeString, "100")
                .subscribeOn(ioScheduler)
                .observeOn(uiScheduler)
                .subscribe(this::handleReturnedData, this::handleError, () -> view.stopLoadingIndicator());
        disposeBag.add(disposable);
    }else {
        // Load new repositories and paginate them with 100 (GitHub API max) repositories par page.
        Disposable disposable = repository.loadLocalRepos(owner, accessTokenString, accessTokenTypeString, "100")
                .subscribeOn(ioScheduler)
                .observeOn(uiScheduler)
                .subscribe(this::handleReturnedData, this::handleError, () -> view.stopLoadingIndicator());
        disposeBag.add(disposable);
    }

}

/**
 * Updates view after loading data is completed successfully.
 */
private void handleReturnedData(List<Repo> list) {
    view.stopLoadingIndicator();
    if (list != null && !list.isEmpty()) {
        view.showRepos(list);
    } else {
        view.showNoDataMessage();
    }
}

/**
 * Updates view if there is an error after loading data from repository.
 */
private void handleError(Throwable error) {
    if(error.getMessage().equalsIgnoreCase("http 403 forbidden")){
        view.startLogin();
    }else {
        view.stopLoadingIndicator();
        view.showErrorMessage(error.getLocalizedMessage());
    }
}

@Override public void getRepo(int repoId) {
    Disposable disposable = repository.getRepo(repoId)
            .filter(repo -> repo != null)
            .subscribeOn(ioScheduler)
            .observeOn(uiScheduler)
            .subscribe(repo -> view.showRepositoryDetail(repo));
    disposeBag.add(disposable);
}
}

रिपॉजिटरी प्रेजेंटरटेस्ट:

import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
//other imports omitted

@RunWith(MockitoJUnitRunner.class)
public class RepositoriesPresenterTest {
    private static final Repo REPO1 = new Repo();
    private static final Repo REPO2 = new Repo();
    private static final Repo REPO3 = new Repo();
    private static final List<Repo> NO_REPOS = Collections.emptyList();
    private static final List<Repo> THREE_REPOS = Arrays.asList(REPO1, REPO2, REPO3);
    public static final String OWNER = "owner";
    public static final String ACCESS_TOKEN_STRING = "access_token_string";
    public static final String ACCESS_TOKEN_TYPE = "access_token_type";
    public static final String PER_PAGE_VALUE = "per_page_value";

    @Mock private GitHubChallengeRepository repositoryMock;

    @Mock private RepositoriesContract.View viewMock;

    private TestScheduler testScheduler;

    private RepositoriesPresenter SUT;  //System Under Test

    @Before public void setUp() {
        MockitoAnnotations.initMocks(this);
        testScheduler = new TestScheduler();
        SUT = new RepositoriesPresenter(repositoryMock, viewMock, testScheduler, testScheduler);
    }

    @Test public void repoPresenter_reposReturned_showReposOnViewExpected() {
        // Given
        given(repositoryMock.loadRemoteRepos(  //this is line 128
            OWNER,
            ACCESS_TOKEN_STRING,
            ACCESS_TOKEN_TYPE,
        PER_PAGE_VALUE)).willReturn(Observable.just(THREE_REPOS));

        // When
        SUT.presenterLoadRepos(true, OWNER);  //this is line 135
        testScheduler.triggerActions();

        // Then
        then(viewMock).should().showRepos(THREE_REPOS);
        then(viewMock).should(atLeastOnce()).stopLoadingIndicator();
    }
} 

जब मैं परीक्षण चलाता हूं तो मुझे यही मिलता है:

java.lang.NullPointerException
at link.mgiannone.githubchallenge.ui.repositories.RepositoriesPresenter.presenterLoadRepos(RepositoriesPresenter.java:189)
at link.mgiannone.githubchallenge.ui.repositories.RepositoriesPresenterTest.repoPresenter_reposReturned_showReposOnViewExpected(RepositoriesPresenterTest.java:138)
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.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:68)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:74)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:161)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
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)
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 com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

[MockitoHint] RepositoriesPresenterTest.repoPresenter_reposReturned_showReposOnViewExpected (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at link.mgiannone.githubchallenge.ui.repositories.RepositoriesPresenterTest.repoPresenter_reposReturned_showReposOnViewExpected(RepositoriesPresenterTest.java:131)
[MockitoHint]  ...args ok? -> at link.mgiannone.githubchallenge.ui.repositories.RepositoriesPresenter.presenterLoadRepos(RepositoriesPresenter.java:188)


Process finished with exit code 255

स्टैकट्रेस से ऐसा लगता है कि मैं एक तर्क का उपयोग नहीं कर रहा हूँ, मैं क्या गलत कर रहा हूँ?

1
makles 3 जिंदा 2019, 13:51

2 जवाब

सबसे बढ़िया उत्तर

आपके प्रस्तुतकर्ता में, loadRemoteRepos को "100" के perPageValue के साथ बुलाया जाता है, लेकिन आपके परीक्षण में, repositoryMock का given भाग केवल के पैरामीटर मान से मेल खाता है "per_page_value".

या तो अंतिम पैरामीटर का मिलान anyString से करें (इस मामले में अन्य सभी पैरामीटर को eq मैचर में लपेटा जाना चाहिए), या परीक्षण में उसी मान का उपयोग करें जैसा कि प्रस्तुतकर्ता कोड में है, या मान को इंजेक्ट करें प्रस्तुतकर्ता के निर्माता के माध्यम से।

1
Janos Breuer 8 जिंदा 2019, 12:20

चूंकि आपका भंडार एक नकली उदाहरण है, इसलिए आपको इसके सभी तरीकों के लिए प्रतिक्रियाओं का मजाक उड़ाना होगा। इसमें ये दोनों शामिल हैं:

String accessTokenString = repository.getAccessTokenString();
String accessTokenTypeString = repository.getAccessTokenType();

तो आपको उनके लिए भी दिए गए कथन चाहिए। तर्ज पर कुछ:

given(repositoryMock.getAccessTokenString()).willReturn("A string")
given(repositoryMock.getAccessTokenType()).willReturn("A string")
1
Levi Moreira 3 जिंदा 2019, 14:13