Skip to content

Commit

Permalink
Merge pull request #8 from pixielabs/android
Browse files Browse the repository at this point in the history
Android Native Reporter
  • Loading branch information
AbigailMcP authored Jun 4, 2019
2 parents 6ecd48d + 23d0fc0 commit 4e71e50
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 66 deletions.
114 changes: 99 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Cavy Native Reporter provides an alternative reporter for Cavy which fires
a Native Module callback when tests are finished. You can then wire this
in to a native test runner such as XCTest (examples below).

You may want to do this if you already have some application tests that are
You may want to do this if you already have some application tests that are
native, e.g. if you already use XCTest to test parts of your app. This could
be because not all of your app is React Native, or if you app makes heavy
use of native code. You may also want to use it if you have an existing
Expand Down Expand Up @@ -83,12 +83,10 @@ cavy-native-reporter overrides this functionality.
## Reporting to Native Tests

### iOS XCTest (Objective C)
`CavyNativeReporter` has a method `onFinishWithBlock` that you can call from
your native tests.

`onFinishWithBlock` takes a block with a single argument, which is the report
object from Cavy. The block is called as soon as the test report is available
from Cavy and the report has the following structure:
##### `onFinishWithBlock`
Call `onFinishWithBlock` from within your native code and pass in a block
taking the single argument, `report`. Your block will be called as soon as the
test report is available from Cavy.

The `report` argument passed to the block is an `NSDictionary` with the
following structure:
Expand All @@ -110,17 +108,17 @@ following structure:
}
```

So if you need to, it's possible to iterate over the results and log more
detailed messages.

If you need to, you can iterate over the test results and log more detailed
messages.

To set up your own XCTestCase using `cavy-native-reporter`:
#### Example
To set up your own XCTestCase that makes use of `cavy-native-reporter`:
1. Open your project's `.xcodeproj` (or `.xcworkspace`) in Xcode.
2. In the Project navigator view, navigate to the folder containing your XCTest
test cases.
3. Create a new test case (select New File -> Unit Test Case Class).
4. Import `<CavyNativeReporter/CavyNativeReporter.h>`
5. Write your expectation!
5. Write your test!

Taking the sample app as an example, we have an XCTestCase `BridgeTest` which
waits for Cavy tests to run and fails if any test returns an error:
Expand All @@ -136,16 +134,21 @@ waits for Cavy tests to run and fails if any test returns an error:
@implementation BridgeTest

- (void)testBridge {
// Make a new expectation.
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription: @"Cavy tests passed"];

[CavyNativeReporter onFinishWithBlock: ^void(NSDictionary* report) {
// Pull the error count from the report object.
long errorCount = [report[@"errorCount"] integerValue];
// Fail if there are errors.
if (errorCount > 0) {
XCTFail(@"Cavy tests had one or more errors");
}
// Fulfill the expectation.
[expectation fulfill];
}];

// Wait for expectation to fulfill.
[self waitForExpectations:@[expectation] timeout:100];
}

Expand All @@ -161,7 +164,7 @@ prompted to choose a language.
usually prompt you to create one if this is your first Swift file in the
project).
3. Import `<CavyNativeReporter/CavyNativeReporter.h>` in your Bridging Header.
4. Write your expectation!
4. Write your test!
The following Swift code is equivalent to the Objective-C example above (note
that the method `onFinishWithBlock` is renamed when you reference it in Swift):
Expand All @@ -187,9 +190,90 @@ class BridgeTest: XCTestCase {
}
```

#### Android
#### Useful links for iOS Testing
- [Writing XCTest classes](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/04-writing_tests.html)
- [Running tests](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/05-running_tests.html#//apple_ref/doc/uid/TP40014132-CH5-SW1)
- [Overview of testing with XCTest](https://www.objc.io/issues/15-testing/xctest/)

### Android
##### `waitForReport`
Call the `waitForReport` method from within your native code to wait for a test
report to be available from Cavy.

##### `cavyReport`
Call the static member variable `cavyReport` from within your native code to
access the test report from Cavy.

#### Example
To set up your own JUnit test that makes use of `cavy-native-reporter`:

1. Open your project's `android` folder in Android Studio.
2. Create a file for your instrumented Android tests at
`module-name/src/androidTest/java/`. Switching to Project view in Android
studio should help with this. [Follow this link for more detailed instructions on setting up Instumented Android tests](https://developer.android.com/studio/test#test_types_and_location) i.e. tests that run on an Android device
or emulator.
3. Add the following dependencies to `android/app/build.gradle` under
`dependencies` (don't touch the `build.gradle` in the app folder itself!):

```java
dependencies: {
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
...
}
```
4. Add the following to `android/app/build.gradle` under `defaultConfig`:

```java
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
...
}
```
5. Write your test!


Taking the sample app as an example, we have an JUnit test `BridgeTest` which
waits for Cavy tests to run and fails if any test returns an error:

```java
package com.sampleapp.bridgetest;

import androidx.test.rule.ActivityTestRule;

import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.*;

import com.cavynativereporter.RNCavyNativeReporterModule;
// This should be the identifier for your own app's main activity.
import com.sampleapp.MainActivity;

public class BridgeTest {
// This rule launches the main activity before each test annotated with @Test.
@Rule
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule(MainActivity.class);

@Test
public void testBridge() throws Exception {
// Wait 60 seconds to receive a test report from Cavy.
RNCavyNativeReporterModule.waitForReport(60);
// Pull the error count from the report object.
double errorCount = RNCavyNativeReporterModule.cavyReport.getDouble("errorCount");
// Note: Third argument is the `delta` allowed between the actual and
// expected double value.
assertEquals(0.0, errorCount, 0.0);
}
}

```

#### Useful links for Android Testing
- [Writing instrumented unit tests](https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests)
- [Using JUnit4 Rules](https://developer.android.com/training/testing/junit-rules)
- [Using the JUnit Runner](https://developer.android.com/training/testing/junit-runner)

TBD!

## Contributing
Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
Expand Down
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ buildscript {
apply plugin: 'com.android.library'

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
compileSdkVersion 28
buildToolsVersion "28.0.3"

defaultConfig {
minSdkVersion 16
Expand All @@ -31,5 +31,5 @@ repositories {
}

dependencies {
compile 'com.facebook.react:react-native:+'
implementation 'com.facebook.react:react-native:+'
}
3 changes: 1 addition & 2 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactlibrary">
package="com.cavynativereporter">

</manifest>

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

package com.cavynativereporter;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

// Android Bridge Module that exposes Cavy test reports to native code.
public class RNCavyNativeReporterModule extends ReactContextBaseJavaModule {

private final ReactApplicationContext reactContext;

public RNCavyNativeReporterModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}

@Override
public String getName() {
return "CavyNativeReporter";
}

public static ReadableMap cavyReport = null;
// Initialize a CountDownLatch with a count of one as a simple gate.
// The thread calling `waitForReport` will wait at `latch.await` until the
// thread called `reporter` opens the gate with `latch.countdown`.
final static CountDownLatch latch = new CountDownLatch(1);

// This method is exposed to JS through React Native NativeModules.
//
// Sets the report as a class variable so we can access it later and opens
// the latch gate (see note on CountDownLatch above).
//
// `report` - the Cavy test report.
@ReactMethod
public void reporter(ReadableMap report) {
cavyReport = report;
latch.countDown();
}

// Class method that waits for the cavyReport to be available (see note on
// CountDownLatch above).
//
// `seconds` - integer representing the number of seconds the latch should
// wait for the report from Cavy before timing out.
public static void waitForReport(int seconds) throws Exception {
if (cavyReport != null) {
return;
} else {
latch.await(seconds, TimeUnit.SECONDS);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

package com.reactlibrary;
package com.cavynativereporter;

import java.util.Arrays;
import java.util.Collections;
Expand All @@ -10,10 +10,11 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class CavyNativeReporterPackage implements ReactPackage {

public class RNCavyNativeReporterPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new CavyNativeReporterModule(reactContext));
return Arrays.<NativeModule>asList(new RNCavyNativeReporterModule(reactContext));
}

// Deprecated from RN 0.47
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion ios/CavyNativeReporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// CavyNativeReporter
//
// Created by Abigail McPhillips on 15/05/2019.
// Copyright © 2019 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
Expand Down
39 changes: 24 additions & 15 deletions ios/CavyNativeReporter.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,44 @@
// CavyNativeReporter
//
// Created by Abigail McPhillips on 15/05/2019.
// Copyright © 2019 Facebook. All rights reserved.
//

#import "CavyNativeReporter.h"
#import <React/RCTLog.h>

// iOS Bridge Module that exposes Cavy test reports to native code.
@implementation CavyNativeReporter

static void (^callback)(NSDictionary *) = nil;
static NSDictionary *cavyReport = nil;

RCT_EXPORT_MODULE(CavyNativeReporter);

RCT_EXPORT_METHOD(reporter:(NSDictionary *)report)
{
if (callback) {
callback(report);
} else {
cavyReport = report;
}
// This method is exposed to JS through React Native NativeModules.
//
// Calls the callback with the report if callback available.
// If not, sets the report as a class variable so we can access it later.
//
// `report` - the Cavy test report.
RCT_EXPORT_METHOD(reporter:(NSDictionary *)report) {
if (callback) {
callback(report);
} else {
cavyReport = report;
}
}

+ (void) onFinishWithBlock:(void (^)(NSDictionary *))block
{
if (cavyReport) {
block(cavyReport);
} else {
callback = block;
}
// Class method that calls the block with the report if report is available
// (i.e. if Cavy tests have finished).
// If not, sets the callback as a class variable so we can access it later.
//
// `block` - a callback function.
+ (void) onFinishWithBlock:(void (^)(NSDictionary *))block {
if (cavyReport) {
block(cavyReport);
} else {
callback = block;
}
}

@end
5 changes: 5 additions & 0 deletions sampleApp/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
splits {
abi {
Expand Down Expand Up @@ -138,6 +139,10 @@ android {
}

dependencies {
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
implementation project(':cavy-native-reporter')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules
Expand Down
Loading

0 comments on commit 4e71e50

Please sign in to comment.