Merge pull request #90 from greena13/dev_focused_pending_tests

[Test Suite] Test Suite Improvements
This commit is contained in:
Michael Diarmid 2017-05-06 15:23:23 +01:00 committed by GitHub
commit 71a9bf4857
7 changed files with 155 additions and 64 deletions

1
tests/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__tests__/build/

View File

@ -103,6 +103,84 @@ function pendingTestTests({ it: _it, describe: _describe }) {
otherTest.should.be.called(); otherTest.should.be.called();
}); });
}); });
_describe('when an outer context is focused', () => {
_it('a pending test will still not run', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const unfocusedTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ fdescribe, it, xit }) => {
fdescribe('', () => {
xit('', pendingTest);
it('', otherTest);
});
it('', unfocusedTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
memo.push(testId);
}
return memo;
}, []);
await testSuite.run(testIdsToRun);
pendingTest.should.not.be.called();
otherTest.should.be.called();
unfocusedTest.should.not.be.called();
});
});
_describe('when an outer context is focused', () => {
_it('a pending context will still not run', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const unfocusedTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ fdescribe, it, xdescribe }) => {
fdescribe('', () => {
xdescribe('', () => {
it('', pendingTest);
});
it('', otherTest);
});
it('', unfocusedTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
memo.push(testId);
}
return memo;
}, []);
await testSuite.run(testIdsToRun);
pendingTest.should.not.be.called();
otherTest.should.be.called();
unfocusedTest.should.not.be.called();
});
});
} }
export default pendingTestTests; export default pendingTestTests;

View File

@ -286,8 +286,7 @@ class TestRun {
suiteId: this.testSuite.id, suiteId: this.testSuite.id,
status: RunStatus.ERR, status: RunStatus.ERR,
time: Date.now() - this.runStartTime, time: Date.now() - this.runStartTime,
message: `Test suite failed: ${error.message}`, message: `Test suite failed: ${error.message}`
stackTrace: error.stack,
}); });
}); });
} }
@ -306,7 +305,7 @@ class TestRun {
} }
async _safelyRunFunction(func, timeOutDuration, description) { async _safelyRunFunction(func, timeOutDuration, description) {
const syncResultOrPromise = tryCatcher(func); const syncResultOrPromise = captureThrownErrors(func);
if (syncResultOrPromise.error) { if (syncResultOrPromise.error) {
// Synchronous Error // Synchronous Error
@ -314,49 +313,59 @@ class TestRun {
} }
// Asynchronous Error // Asynchronous Error
return promiseToCallBack(syncResultOrPromise.value, timeOutDuration, description); return capturePromiseErrors(syncResultOrPromise.result, timeOutDuration, description);
} }
} }
/** /**
* Try catch to object * Call a function and capture any errors that are immediately thrown.
* @returns {{}} * @returns {Object} Object containing result of executing the function, or the error
* message that was captured
* @private * @private
*/ */
function tryCatcher(func) { function captureThrownErrors(func) {
const result = {}; const result = {};
try { try {
result.value = func(); result.result = func();
} catch (e) { } catch (error) {
result.error = e; result.error = error;
} }
return result; return result;
} }
/** /**
* Make a promise callback-able to trap errors * Wraps a promise so that if it's rejected or an error is thrown while it's being
* @param promise * evaluated, it's captured and thrown no further
* @param {*} target - Target to wrap. If a thenable object, it's wrapped so if it's
* rejected or an error is thrown, it will be captured. If a non-thenable object,
* wrapped in resolved promise and returned.
* @param {Number} timeoutDuration - Number of milliseconds the promise is allowed
* to pend before it's considered timed out
* @param {String} description - Description of the context the promises is defined
* in, used for reporting where a timeout occurred in the resulting error message.
* @private * @private
*/ */
function promiseToCallBack(promise, timeoutDuration, description) { function capturePromiseErrors(target, timeoutDuration, description) {
let returnValue = null; let returnValue = null;
try { try {
returnValue = Promise.resolve(promise) returnValue = Promise.resolve(target)
.then(() => { .then(() => {
return null; return null;
}, (error) => { }, (error) => {
return Promise.resolve(error); return Promise.resolve(error);
}) })
.timeout(timeoutDuration, `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`)
.catch((error) => { .catch((error) => {
return Promise.resolve(error); return Promise.resolve(error);
}); })
.timeout(timeoutDuration,
`${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`
);
} catch (error) { } catch (error) {
returnValue = Promise.resolve(error); returnValue = Promise.resolve(error);
} }

View File

@ -111,19 +111,19 @@ class TestSuite {
*/ */
async run(testIds = undefined) { async run(testIds = undefined) {
const testsToRun = (() => { const testsToRun = (() => {
if (testIds) { return (testIds || Object.keys(this.testDefinitions.tests)).reduce((memo, id) => {
return testIds.map((id) => { const test = this.testDefinitions.tests[id];
const test = this.testDefinitions.tests[id];
if (!test) { if (!test) {
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`); throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
} }
return test; if (!this.testDefinitions.pendingTestIds[id]) {
}); memo.push(test);
} }
return Object.values(this.testDefinitions.tests); return memo;
}, []);
})(); })();
const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions); const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions);

View File

@ -14,13 +14,14 @@ export function setSuiteStatus({ suiteId, status, time, message, progress }) {
}; };
} }
export function setTestStatus({ testId, status, time = 0, message = null }) { export function setTestStatus({ testId, status, stackTrace, time = 0, message = null }) {
return { return {
type: TEST_SET_STATUS, type: TEST_SET_STATUS,
testId, testId,
status, status,
message, message,
stackTrace,
time, time,
}; };
} }

View File

@ -12,6 +12,7 @@ function testsReducers(state = initState.tests, action: Object): State {
flattened[`${action.testId}.status`] = action.status; flattened[`${action.testId}.status`] = action.status;
flattened[`${action.testId}.message`] = action.message; flattened[`${action.testId}.message`] = action.message;
flattened[`${action.testId}.time`] = action.time; flattened[`${action.testId}.time`] = action.time;
flattened[`${action.testId}.stackTrace`] = action.stackTrace;
return unflatten(flattened); return unflatten(flattened);
} }

View File

@ -51,35 +51,28 @@ class Test extends React.Component {
setParams({ test }); setParams({ test });
} }
renderError() {
const { test: { message } } = this.props;
if (message) {
return (
<ScrollView>
<Text style={styles.codeHeader}>Test Error</Text>
<Text style={styles.code}>
<Text>{message}</Text>
</Text>
</ScrollView>
);
}
return null;
}
render() { render() {
const { test: { func, status, time } } = this.props; const { test: { stackTrace, description, func, status, time }, testContextName } = this.props;
return ( return (
<View style={styles.container}> <View style={styles.container}>
{Test.renderBanner({ status, time })} {Test.renderBanner({ status, time })}
<View style={styles.content}> <View >
{this.renderError()} <ScrollView style={styles.sectionContainer}>
<Text style={styles.codeHeader}>Test Code Preview</Text> <Text style={styles.heading}>{testContextName}</Text>
<ScrollView> <Text style={styles.description}>{description}</Text>
<Text style={styles.code}> </ScrollView>
<ScrollView style={styles.sectionContainer}>
<Text style={styles.heading}>Test Error</Text>
<Text style={styles.description}>
<Text>{stackTrace || 'None.'}</Text>
</Text>
</ScrollView>
<Text style={styles.heading}>
Test Code Preview
</Text>
<ScrollView style={styles.sectionContainer}>
<Text style={styles.description}>
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })} {beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
</Text> </Text>
</ScrollView> </ScrollView>
@ -93,10 +86,13 @@ Test.propTypes = {
test: PropTypes.shape({ test: PropTypes.shape({
status: PropTypes.string, status: PropTypes.string,
time: PropTypes.number, time: PropTypes.number,
message: PropTypes.string,
func: PropTypes.function, func: PropTypes.function,
stackTrace: PropTypes.function,
description: PropTypes.string,
}).isRequired, }).isRequired,
testContextName: PropTypes.string,
navigation: PropTypes.shape({ navigation: PropTypes.shape({
setParams: PropTypes.func.isRequired, setParams: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
@ -107,27 +103,32 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
}, },
content: {}, sectionContainer: {
code: { minHeight: 100,
backgroundColor: '#3F373A',
color: '#c3c3c3',
padding: 5,
fontSize: 12,
}, },
codeHeader: { heading: {
fontWeight: '600',
fontSize: 18,
backgroundColor: '#000',
color: '#fff',
padding: 5, padding: 5,
backgroundColor: '#0288d1',
fontWeight: '600',
color: '#ffffff',
fontSize: 16,
},
description: {
padding: 5,
fontSize: 14,
}, },
}); });
function select({ tests }, { navigation: { state: { params: { testId } } } }) { function select({ tests, testContexts }, { navigation: { state: { params: { testId } } } }) {
const test = tests[testId]; const test = tests[testId];
let testContext = testContexts[test.testContextId];
while(testContext.parentContextId && testContexts[testContext.parentContextId].parentContextId) {
testContext = testContexts[testContext.parentContextId];
}
return { return {
test, test,
testContextName: testContext.name,
}; };
} }