Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #10 from novoda/mockito_based_mocks
Browse files Browse the repository at this point in the history
Mockito based mocks
  • Loading branch information
ataulm committed Aug 27, 2015
2 parents 42c9552 + c14bc56 commit 0554d09
Show file tree
Hide file tree
Showing 26 changed files with 1,127 additions and 387 deletions.
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ Easy Espresso UI testing for Android applications using RxJava.

RxPresso makes testing your presentation layer using RxJava as easy as a Unit test.

RxPresso uses [RxMocks](https://github.com/novoda/rxmocks) to generate mocks of your repositories that you can use with RxPresso to control data in your Espresso tests.
RxPresso uses [Mockito](http://mockito.org/) to generate mocks of your repositories that you can use with RxPresso to control data in your Espresso tests.
The binding with Espresso Idling resource is handled for you so Espresso will wait until the data you expect to inject in your UI
has been delivered to you UI.

No more data you don't control in your Espresso test.

At the moment this will only mock methods from the interface returning observables (see Future improvements section).

This project is in its early stages, feel free to comment, and contribute back to help us improve it.

## Adding to your project
Expand All @@ -26,18 +24,17 @@ buildscript {
jcenter()
}
dependencies {
androidTestCompile 'com.novoda:rxpresso:0.1.5'
androidTestCompile 'com.novoda:rxpresso:0.2.0'
}
}
```


## Simple usage

To generate a mocked repo use an interface providing Observables as an abstraction for your repo.
You can now use this interface to generate a mock as shown below.
To generate a mocked repo simply use Mockito.

**Interfaced repository**
**Example repository**
```java
public interface DataRepository {

Expand All @@ -50,7 +47,7 @@ public interface DataRepository {

**Mocking this repository**
```java
DataRepository mockedRepo = RxMocks.mock(DataRepository.class)
DataRepository mockedRepo = Mockito.mock(DataRepository.class)
```

You should then replace the repository used by your activities by this mocked one.
Expand All @@ -62,7 +59,7 @@ Any other option as long as your UI reads from the mocked repo.
```java
DataRepository mockedRepo = getSameRepoUsedByUi();

RxPresso rxpresso = new RxPresso(mockedRepo);
RxPresso rxpresso = RxPresso.init(mockedRepo);
Espresso.registerIdlingResources(rxPresso);
```

Expand Down Expand Up @@ -131,14 +128,10 @@ DataRepository mockedRepo = getSameRepoUsedByUi();
AnotherDataRepository mockedRepo2 = getSameSecondRepoUsedByUi();


RxPresso rxpresso = new RxPresso(mockedRepo, mockedRepo2);
RxPresso rxpresso = RxPresso.init(mockedRepo, mockedRepo2);
Espresso.registerIdlingResources(rxPresso);
```

## Future improvements

- Support "spying" to allow for non mocked calls to be forwarded to actual implementation.

## Links

Here are a list of useful links:
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.2'
classpath 'com.novoda:bintray-release:0.2.10'
classpath 'com.novoda:bintray-release:0.3.2'
}
}

Expand Down
9 changes: 5 additions & 4 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ android {
}

dependencies {
compile 'com.novoda:rxmocks:0.1.3'
compile 'io.reactivex:rxjava:1.0.10'
compile 'io.reactivex:rxjava:1.0.14'
compile 'com.android.support.test.espresso:espresso-core:2.1'
compile 'org.mockito:mockito-core:1.10.19'
compile 'com.google.dexmaker:dexmaker:1.2'
compile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'org.easytesting:fest-assert-core:2.0M10'
testCompile 'org.mockito:mockito-core:1.10.19'
}

publish {
repoName = 'maven'
userOrg = 'novoda'
groupId = 'com.novoda'
artifactId = 'rxpresso'
publishVersion = '0.1.5'
publishVersion = '0.2.0'
description = 'Easy espresso testing for projects using RxJava'
website = 'https://github.com/novoda/rxpresso'
}
70 changes: 34 additions & 36 deletions core/src/main/java/com/novoda/rxpresso/Expect.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import android.support.test.espresso.IdlingResource;

import com.novoda.rxmocks.RxExpect;
import com.novoda.rxmocks.RxMatcher;
import com.novoda.rxmocks.RxMocks;
import com.novoda.rxpresso.matcher.RxExpect;
import com.novoda.rxpresso.matcher.RxMatcher;
import com.novoda.rxpresso.mock.RxMock;

import java.util.concurrent.atomic.AtomicBoolean;

Expand All @@ -16,16 +16,16 @@

public class Expect<T> implements IdlingResource {

private final Object repo;
private final Observable<T> observable;
private final RxMock mock;
private final Observable<T> source;
private final AtomicBoolean idle = new AtomicBoolean(true);

private Subscription subscription;
private ResourceCallback resourceCallback;

Expect(Object repo, Observable<T> source, Observable<T> observable) {
this.repo = repo;
Expect(RxMock mock, Observable<T> source, Observable<T> observable) {
this.mock = mock;
this.source = source;
this.observable = observable;
}
Expand All @@ -40,9 +40,7 @@ public class Expect<T> implements IdlingResource {
*/
public Then expect(RxMatcher<Notification<T>> matcher) {
expectAnyMatching(matcher);
RxMocks.with(repo)
.sendEventsFrom(source)
.to(observable);
mock.sendEventsFrom(source).to(observable);
return new Then();
}

Expand All @@ -55,44 +53,44 @@ public Then expect(RxMatcher<Notification<T>> matcher) {
*/
public Then expectOnly(RxMatcher<Notification<T>> matcher) {
expectOnlyMatching(matcher);
RxMocks.with(repo)
.sendEventsFrom(source)
.to(observable);
mock.sendEventsFrom(source).to(observable);
return new Then();
}

private void expectAnyMatching(RxMatcher<Notification<T>> matcher) {
RxErrorRethrower.register();
idle.compareAndSet(true, false);
subscription = RxMocks.with(repo)
.getEventsFor(observable)
.subscribe(
RxExpect.expect(
matcher, new Action1<Notification<T>>() {
@Override
public void call(Notification<T> notification) {
subscription.unsubscribe();
RxErrorRethrower.unregister();
transitionToIdle();
}
}));

subscription = mock.getEventsFor(observable).subscribe(
RxExpect.expect(
matcher, new Action1<Notification<T>>() {
@Override
public void call(Notification<T> tNotification) {
subscription.unsubscribe();
RxErrorRethrower.unregister();
transitionToIdle();
}
}
)
);
}

private void expectOnlyMatching(RxMatcher<Notification<T>> matcher) {
RxErrorRethrower.register();
idle.compareAndSet(true, false);
subscription = RxMocks.with(repo)
.getEventsFor(observable)
.subscribe(
RxExpect.expectOnly(
matcher, new Action1<Notification<T>>() {
@Override
public void call(Notification<T> notification) {
subscription.unsubscribe();
RxErrorRethrower.unregister();
transitionToIdle();
}
}));

subscription = mock.getEventsFor(observable).subscribe(
RxExpect.expectOnly(
matcher, new Action1<Notification<T>>() {
@Override
public void call(Notification<T> tNotification) {
subscription.unsubscribe();
RxErrorRethrower.unregister();
transitionToIdle();
}
}
)
);
}

private void transitionToIdle() {
Expand Down
77 changes: 41 additions & 36 deletions core/src/main/java/com/novoda/rxpresso/RxPresso.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import android.support.test.espresso.IdlingResource;

import com.novoda.rxmocks.RxMocks;
import com.novoda.rxpresso.mock.RxMock;

import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -11,43 +11,54 @@
import rx.Observable;
import rx.functions.Func1;

public class RxPresso implements IdlingResource {
public final class RxPresso implements IdlingResource {

private final Object[] repositories;
private final List<RxMock> mocks;
private final List<IdlingResource> pendingResources = Collections.synchronizedList(new ArrayList<IdlingResource>());

private ResourceCallback resourceCallback;

/**
* @param repositories The different mocked repositories you want to control in your tests
* @param mocks The different mocked repositories you want to control in your tests
*/
public RxPresso(Object... repositories) {
this.repositories = repositories;
public static RxPresso from(Object... mocks) {
return new RxPresso(Observable.from(mocks).map(asRxMocks).toList().toBlocking().first());
}

/**
/**
* Initiate an action on the given mocked {@code observable}
* @param observable The mocked observable to work with
* @param <T> The type of the observable
* @return The With object to interact with the given mocked {@code observable}
* @param mocks The different mocked repositories you want to control in your tests
*/
private RxPresso(List<RxMock> mocks) {
this.mocks = mocks;
}

public <T> With<T> given(Observable<T> observable) {
Object repo = Observable.from(repositories).filter(provides(observable)).toBlocking().first();
final With<T> with = new With<>(repo, observable);
RxMock mock = Observable.from(mocks).filter(provides(observable)).toBlocking().first();
final With<T> with = new With<>(mock, observable);
pendingResources.add(with);
with.registerIdleTransitionCallback(new ResourceCallback() {
@Override
public void onTransitionToIdle() {
pendingResources.remove(with);
if (pendingResources.isEmpty()) {
resourceCallback.onTransitionToIdle();
with.registerIdleTransitionCallback(
new ResourceCallback() {
@Override
public void onTransitionToIdle() {
pendingResources.remove(with);
if (pendingResources.isEmpty()) {
resourceCallback.onTransitionToIdle();
}
}
}
}
});
);
return with;
}

private static <T> Func1<? super RxMock, Boolean> provides(final Observable<T> observable) {
return new Func1<RxMock, Boolean>() {
@Override
public Boolean call(RxMock rxMock) {
return rxMock.provides(observable);
}
};
}

@Override
public String getName() {
return "RxPresso";
Expand All @@ -66,29 +77,23 @@ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}

/**
* Resets all the mocked observables from the repositories registered by RxPresso
*/
public void resetMocks() {
for (Object repo : repositories) {
RxMocks.with(repo).resetMocks();
for (RxMock mock : mocks) {
mock.resetMocks();
}
}

private static <T> Func1<Object, Boolean> provides(final Observable<T> observable) {
return new Func1<Object, Boolean>() {
@Override
public Boolean call(Object repo) {
return RxMocks.with(repo).provides(observable);
}
};
}

private static Func1<IdlingResource, Boolean> isIdle = new Func1<IdlingResource, Boolean>() {
private static final Func1<IdlingResource, Boolean> isIdle = new Func1<IdlingResource, Boolean>() {
@Override
public Boolean call(IdlingResource resource) {
return resource.isIdleNow();
}
};

private static final Func1<Object, RxMock> asRxMocks = new Func1<Object, RxMock>() {
@Override
public RxMock call(Object object) {
return RxMock.from(object);
}
};
}
25 changes: 15 additions & 10 deletions core/src/main/java/com/novoda/rxpresso/With.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,38 @@

import android.support.test.espresso.IdlingResource;

import com.novoda.rxpresso.mock.RxMock;

import rx.Observable;

public class With<T> implements IdlingResource {

private final Object repo;
private final RxMock mock;
private final Observable<T> observable;
private ResourceCallback resourceCallback;
private Expect<T> expect;

With(Object repo, Observable<T> observable) {
this.repo = repo;
With(RxMock mock, Observable<T> observable) {
this.mock = mock;
this.observable = observable;
}

/**
* Setup the injection of the events from the {@code source} into the mocked {@code observable}
*
* @param source An observable providing the events to inject
* @return An Expect object to trigger the injection and setup what event to expect and wait for.
*/
public Expect<T> withEventsFrom(Observable<T> source) {
expect = new Expect<>(repo, source, observable);
expect.registerIdleTransitionCallback(new ResourceCallback() {
@Override
public void onTransitionToIdle() {
resourceCallback.onTransitionToIdle();
}
});
expect = new Expect<>(mock, source, observable);
expect.registerIdleTransitionCallback(
new ResourceCallback() {
@Override
public void onTransitionToIdle() {
resourceCallback.onTransitionToIdle();
}
}
);
return expect;
}

Expand Down
Loading

0 comments on commit 0554d09

Please sign in to comment.