I have a Stream<T> in my flutter app which uses timeout() under hood. Everything works fine, but running unit tests takes a while, since they have to wait every time for the timeout.
As I can see from sources timeout() implementation is based on Timer(). Is there any way to artificially advance time for Timer() on unit test environment?
For example in RxJava there is TestScheduler which has methods like advanceTimeBy/advanceTimeTo. I am looking for similar functionality.
Update:
The code could look like this:
extension StreamEx<T> on Stream<T> {
/// timeout only the first emission
Stream<T> timeoutFirst(Duration timeLimit) =>
MergeStream([
take(1).timeout(timeLimit),
skip(1),
]);
}
My requirement for the test here is that its runtime should not depend on whatever passed as timeLimit to the method. It possible with RxJava but how to fix it at Darts streams.
Update:
The test could look like this:
void main() async {
group(
'StreamEx.timeoutFirst',
() {
const TIMEOUT = Duration(milliseconds: 100);
const DELAY = Duration(milliseconds: 110);
test(
'should throw `TimeoutException` if it took too long for the first emission',
() async {
final subject = BehaviorSubject<int>();
final stream = subject.timeoutFirst(TIMEOUT).dump('tag0');
expectLater(
stream,
emitsInOrder([
emitsError(isInstanceOf<TimeoutException>()),
emits(equals(1)),
emits(equals(2)),
]),
);
await Future.delayed(DELAY);
subject.add(1);
subject.add(2);
subject.close();
},
);
},
);
}
Related
Have been trying to solve the issue for a while.
Currently I have an array of objects (i call them tiles), which is pretty big.
I have an API endpoint where I should send this objects one by one, this API returns nothing, just status.
I need to send this objects to endpoint in parallel and concurrent manner and when the last of them is successful I should emit some string value which goes to redux store.
const tilesEpic =(action$, _state$) => {
action$.pipe(
ofType('TILE_ACTION'),
map(tilesArray => postTilesConcurrently(tilesArray),
map(someId => someReduxAction(someId),
)
const postTilesConcurrently = (tilesArray) => {
const tilesToObservables = tilesArray.map(tile => defer(() => postTile(tile))
return from(tileToObservables).pipe(mergeAll(concurrencyLimit))
}
The problem is that I have no idea how to emit someId from postTilesConcurrently, now it triggers action after each request is complete.
mergeAll() will subscribe to all sources in parallel but it will also emit each result immediatelly. So instead you could use for example forkJoin() (
you could use toArray() operator as well).
forkJoin(tilesToObservables)
.pipe(
map(results => results???), // Get `someId` somehow from results
);
forkJoin() will emit just once after all source Observables emit at least once and complete. This means for each source Observable you'll get only the last value it emitted.
After Martin's reply I have adjusted my code in order to use forkJoin
const tilesEpic =(action$, _state$) => {
action$.pipe(
ofType('TILE_ACTION'),
concatMap(tilesArray => postTilesConcurrently(tilesArray),
map(({someId}) => someReduxAction(someId),
)
const postTilesConcurrently = (tilesArray) => {
const tilesToObservables = tilesArray.map(tile => defer(() => postTile(tile))
return forkJoin({
images: from(tileToObservables).pipe(mergeAll(concurrencyLimit)),
someId: from([someId]),
}
I have an observable which might throw an error. When it throws, I want to resubscribe to that observable and try again. For example with the retry() operator.
To test this retry-logic I would need to create a test-observable which will throw an error the first 2 times it's subscribed to, and only on 3rd time would produce a value.
I tried the following:
import { Observable } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { retry } from 'rxjs/operators';
// Setup for TestScheduler
function basicTestScheduler() {
return new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}
// The function we're going to test
function retryMultipleTimes(observable$) {
return observable$.pipe(retry(2));
}
describe('retryMultipleTimes()', () => {
it('retries twice when observable throws an error', () => {
basicTestScheduler().run(({ hot, cold, expectObservable }) => {
const observable$ = hot('--#--#--Y');
const expected = ' --------Y'; // This is what I want to get
const unexpected = ' --# '; // This is what I get instead
expectObservable(retryMultipleTimes(observable$)).toBe(expected);
});
});
});
Seems that with a hot() observable it always resubscribes to the same frame that produced the error, resulting in immediately throwing again.
I also tried with cold() observable, in which case I get ------# - that is, at each retry the observable starts again from the beginning, resulting in --#, --#, --# - never reaching --Y.
It seems that there isn't a way to do such a thing with RxJS TestScheduler. Or perhaps there is?
If the hot() and cold() observable-creators aren't up to the task, perhaps I can create my own... but how?
I also tried adding a little delay between retries, so I wouldn't resubscribe immediately to the current frame by implementing the retry-logic using retryWhen:
function retryMultipleTimes(observable$) {
return observable$.pipe(
retryWhen(errors => errors.pipe(
delayWhen(() => timer(2)), // wait 2 frames before each retry
take(2), // do maximum of 2 retries
concat(throwError('error')), // finish with error when no success after 2 retries
)),
);
}
But this didn't work either. Looks like the resubscription still happens to the same frame as before.
How could I make this test pass?
Figured out a solution for this. I can use iif() to create an observable which chooses at subscription time between two observables:
describe('retryMultipleTimes()', () => {
it('retries twice when observable throws an error', () => {
basicTestScheduler().run(({ cold, expectObservable }) => {
let count = 0;
const observable$ = iif(
() => ++count <= 2,
cold('--#'),
cold('--Y'),
);
// --#
// --#
// --Y
expectObservable(retryMultipleTimes(observable$)).toBe('------Y');
});
});
});
How do I make an action occur with a delay, but after a timeout?
The setTimeout() function doesn’t work in Decentraland scenes, so is there an alternative?
For example, I want an entity to wait 300 milliseconds after it’s clicked before I remove it from the engine.
To implement this you’ll have to create:
A custom component to keep track of time
A component group to keep track of all the entities with a delay in the scene
A system that updates the timers con all these
components on each frame.
It sounds rather complicated, but once you created one delay, implementing another delay only takes one line.
The component:
#Component("timerDelay")
export class Delay implements ITimerComponent{
elapsedTime: number;
targetTime: number;
onTargetTimeReached: (ownerEntity: IEntity) => void;
private onTimeReachedCallback?: ()=> void
/**
* #param millisecs amount of time in milliseconds
* #param onTimeReachedCallback callback for when time is reached
*/
constructor(millisecs: number, onTimeReachedCallback?: ()=> void){
this.elapsedTime = 0
this.targetTime = millisecs / 1000
this.onTimeReachedCallback = onTimeReachedCallback
this.onTargetTimeReached = (entity)=>{
if (this.onTimeReachedCallback) this.onTimeReachedCallback()
entity.removeComponent(this)
}
}
}
The component group:
export const delayedEntities = engine.getComponentGroup(Delay)
The system:
// define system
class TimerSystem implements ISystem {
update(dt: number){
for (let entity of delayedEntities.entities) {
let timerComponent = entity.getComponent(component)
timerComponent.elapsedTime += dt
if (timerComponent.elapsedTime >= timerComponent.targetTime){
timerComponent.onTargetTimeReached(entity)
}
})
}
}
// instance system
engine.addSystem(new TimerSystem())
Once all these parts are in place, you can simply do the following to delay an execution in your scene:
const myEntity = new Entity()
myEntity.addComponent(new Delay(1000, () => {
log("time ran out")
}))
engine.addEntity(myEntity)
A few years late, but the OP's selected answer is kind of deprecated because you can accomplish a delay doing:
import { Delay } from "node_modules/decentraland-ecs-utils/timer/component/delay"
const ent = new Entity
ent.addComponent(new Delay(3 * 1000, () => {
// this code will run when time is up
}))
Read the docs.
Use the utils.Delay() function in the utils library.
This function just takes the delay time in milliseconds, and the function you want to execute.
Here's the full documentation, explaining how to add the library + how to use this function, including example code:
https://www.npmjs.com/package/decentraland-ecs-utils
Man, this firebase unit testing is really kicking my butt.
I've gone through the documentation and read through the examples that they provide, and have gotten some of my more basic Firebase functions unit tested, but I keep running into problems where I'm not sure how to verify that the transactionUpdated function passed along to the refs .transaction is correctly updating the current object.
My struggle is probably best illustrated with their child-count sample code and a poor attempt I made at writing a unit test for it.
Let's say my function that I want to unit test does the following (taken straight from that above link):
// count.js
exports.countlikechange = functions.database.ref('/posts/{postid}/likes/{likeid}').onWrite(event => {
const collectionRef = event.data.ref.parent;
const countRef = collectionRef.parent.child('likes_count');
// ANNOTATION: I want to verify the `current` value is incremented
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
Unit Test Code:
const chai = require('chai');
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const assert = chai.assert;
const sinon = require('sinon');
describe('Cloud Functions', () => {
let myFunctions, functions;
before(() => {
functions = require('firebase-functions');
myFunctions = require('../count.js');
});
describe('countlikechange', () => {
it('should increase /posts/{postid}/likes/likes_count', () => {
const event = {
// DeltaSnapshot(app: firebase.app.App, adminApp: firebase.app.App, data: any, delta: any, path?: string);
data: new functions.database.DeltaSnapshot(null, null, null, true)
}
const startingValue = 11
const expectedValue = 12
// Below code is misunderstood piece. How do I pass along `startingValue` to the callback param of transaction
// in the `countlikechange` function, and spy on the return value to assert that it is equal to `expectedValue`?
// `yield` is almost definitely not the right thing to do, but I'm not quite sure where to go.
// How can I go about "spying" on the result of a stub,
// since the stub replaces the original function?
// I suspect that `sinon.spy()` has something to do with the answer, but when I try to pass along `sinon.spy()` as the yields arg, i get errors and the `spy.firstCall` is always null.
const transactionStub = sinon.stub().yields(startingValue).returns(Promise.resolve(true))
const childStub = sinon.stub().withArgs('likes_count').returns({
transaction: transactionStub
})
const refStub = sinon.stub().returns({ parent: { child: childStub }})
Object.defineProperty(event.data, 'ref', { get: refStub })
assert.eventually.equals(myFunctions.countlikechange(event), true)
})
})
})
I annotated the source code above with my question, but I'll reiterate it here.
How can I verify that the transactionUpdate callback, passed to the transaction stub, will take my startingValue and mutate it to expectedValue and then allow me to observe that change and assert that it happened.
This is probably a very simple problem with an obvious solution, but I'm very new to testing JS code where everything has to be stubbed, so it's a bit of a learning curve... Any help is appreciated.
I agree that unit testing in the Firebase ecosystem isn't as easy as we'd like it to be. The team is aware of it, and we're working to make things better! Fortunately, there are some good ways forward for you right now!
I suggest taking a look at this Cloud Functions demo that we've just published. In that example we use TypeScript, but this'll all work in JavaScript too.
In the src directory you'll notice we've split out the logic into three files: index.ts has the entry-logic, saythat.ts has our main business-logic, and db.ts is a thin abstraction layer around the Firebase Realtime Database. We unit-test only saythat.ts; we've intentionally kept index.ts and db.ts really simple.
In the spec directory we have the unit tests; take a look at index.spec.ts. The trick that you're looking for: we use mock-require to mock out the entire src/db.ts file and replace it with spec/fake-db.ts. Instead of writing to the real database, we now store our performed operations in-memory, where our unit test can check that they look correct. A concrete example is our score field, which is updated in a transaction. By mocking the database, our unit test to check that that's done correctly is a single line of code.
I hope that helps you do your testing!
I've hit a bit of an interesting road block in my attempt at writing unit tests for some middleware as I can't seem to come up with a feasible means to fake two concurrent connections for a generator function which is a piece of koa middleware.
I have a constructor function that takes some setup options and returns a generator. This generator has access to some variables via closure which increment per request and decrement when the complete. Here is a subset of the code to give you an idea of what i'm trying to accomplish.
module.exports = function (options = {}) {
let connections = 0;
let {
max = 100
...
} = options;
return function *() {
connections++
...
if (connections > max) {
connections--;
// callback here
}
...
}
}
In simple terms I want to be able to keep track of multiple simultaneous "connections" in which I fire a callback when a max number of requests have been met. However, in my test i get back a single instance of this generator and can only call it once mimicking a single request, thus i can never meet the connections > max conditional
it("Should trigger callback when max connections reached", () => {
const gen = middleware({
max: 1,
onMax: function (current, max) {
this.maxReached = true;
}
}).call(context);
gen.next();
expect(context.maxReached).to.be.true;
});
Sometimes you just need a good night sleep to dream your answer. This was simply a matter of calling the same generator with two different contexts that represented two different requests and store a value to tests against on the latter. The counter would still increment because I never returned up the middleware chain (response) in order to decrement. It's more of a fake concurrency.
const middleware = limiter({
max: 1,
onMax: function (current, max) {
this.maxReached = true;
}
});
middleware.call(reqContext).next();
middleware.call(secondReqContext).next();
expect(secondReqContext.maxReached).to.be.true;