According to MSpec documentation here, Establish should run only once. However, when I debug the Establish is being run once per the It delegate. Here is a simple example:
public class TestExample
{
Establish arrange = () =>
{
a = 5;
};
Because of = () => a = a * 2;
It should_be1 = () => a.ShouldEqual(10);
It should_be2 = () => a.ShouldBeGreaterThan(9);
It should_be3 = () => a.ShouldBeLessThan(90);
private static int a;
}
Should it run once per It delegate? Or once per the class?
It's once per context (class) - unless it has a base class in which case the base class's Establish executes first (recursively, as deep as the class inheritance tree).
How are you determining that it is executing multiple times?
For what it's worth, I cannot reproduce this behaviour using MSpec 0.9.0, the ReSharper runner and the exact code from the question. All of the specs execute and the Establish executes once, as expected.
Sprinkling a few Console.WriteLine() as follows:
using System;
using Machine.Specifications;
namespace StackOverflow_26809460
{
public class TestExample
{
static int a;
Establish arrange = () =>
{
a = 5;
Console.WriteLine("Establish");
};
Because of = () => a = a*2;
It should_be1 = () =>
{
a.ShouldEqual(10);
Console.WriteLine("should_be1");
};
It should_be2 = () =>
{
a.ShouldBeGreaterThan(9);
Console.WriteLine("should_be1");
};
It should_be3 = () =>
{
a.ShouldBeLessThan(90);
Console.WriteLine("should_be1");
};
}
}
When I run that in the command line runner, I get:
Specs in StackOverflow_26809460:
TestExample
Establish
should_be1
» should be1
should_be1
» should be2
should_be1
» should be3
Contexts: 1, Specifications: 3, Time: 0.53 seconds
C:\Users\Tim\Projects\StackOverflow\StackOverflow_26809460\bin\Debug>
It looks like a runner issue to me...
Related
I'm unit testing and part of the testing has a Subject. I'm new to Subjects and get the general gist of how they work but am struggling to mock a return value on one. I've tried various ways in the hopes of stumbling on the correct way like using a spy and returnvalue to return the number 3.
In the component:
....
private searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
In my spec file I basically have:
component['searchStockEvent'].subscribe(x=> of(3));
fixture.whenStable().then(() => {
expect(component['retrieveAssets']).toHaveBeenCalled();
});
searchEvent being private will make it difficult to call next directly on the subject so you have to find a way of what makes searchEvent emit a value of greater than 3 and go that route.
For this demo, we will make it public:
....
public searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
import { fakeAsync, tick } from '#angular/core/testing';
it('should call retrieve assets', fakeAsync(() => {
component.searchEvent.next(3);
// we need to pass 501ms in a fake way
tick(501);
expect(component.retreiveAssets).toHaveBeenCalled();
}));
In some of my models I need to define a reaction in the constructor like this:
constructor() {
//...some code
const dispose = reaction(
() => this.items.length,
count => {
this.setItemCount(count);
}
);
}
I am using a reaction rather than a computed (#computed get itemCount()) because loading items into state is an expensive (lots of data over network) operation and so I need to persist the most recent value so that it can be used throughout the app. The reaction is to update the value if the count changes when the items are loaded into state.
So, with the above in mind, I'm wondering when/how I would dispose of the reaction? I want to avoid memory leaks. I'm open to alternative ways of accomplishing what I need although I would prefer a reactive vs imperative approach.
Three ways to go about this.
Dispose of reaction after it is done its job.
constructor() {
const dispose = reaction(
() => this.items.length,
(count, reaction) => {
this.setItemCount(count);
if(count === 100) reaction.dispose()
}
);
}
Some other action disposes it for you. Like a button click. Or another reaction. Whatever you need it to be, actually.
class myStore {
disposer
constructor() {
this.disposer = reaction(
() => this.items.length,
(count) => this.setItemCount(count)
);
}
myButtonClick = () => {
this.disposer()
}
}
Create a "deconstructor" method in your class that is meant to be called when you don't "need" this class/store anymore. You can use this method for dumping in anything that needs a cleanup before safely passing things to garbage collector.
class myStore {
disposers = []
constructor () {
this.disposers.push(reaction(
() => this.items.length,
(count, reaction) => {
this.setItemCount(count);
if(count === 100) reaction.dispose()
}
))
}
deconstructor() {
this.disposers.forEach((disposer) => disposer())
}
}
You are responsible for calling this deconstructor too. Typically you will be calling it on component unmount. Hook example below:
function Example() {
const [store] = useState(() => new myStore())
useEffect(() => {
return () => store.deconstructor()
}, [])
return <App/>
}
If store is global/context, you can call destructor in a frame component (a component that is always mounted in app's lifecycle), so it is run when user exits the app. I am not so sure, however, how needed is this step specifically for Mobx disposables, maybe someone can comment on this. Does not hurt to do it, though.
NB. Actually you should be doing number 3 at all times anyway, because it could be that due to some reasons, condition at 1. (or 2.) might not manage to trigger and you are left with unneeded reaction ticking in the background.
I'm using an array of disposables + specific method to dispose them.
It's looking like:
class MyClass {
...
disposables = [];
...
constructor () {
// constructor stuff
this.disposables.push(reaction(
() => this.items.length,
count => {
this.setItemCount(count);
}
))
}
...
disposeAll = () => {
this.disposables.forEach(dispose => dispose());
}
}
This method is not useful if you want to dispose specific reaction. But in this case you can you map instead of an array.
I am trying to test a simple controller variable but my controller has a method which calls parent controller and showing this error.How do i solve this error.
here is the test file
describe('SigninController', function () {
beforeEach(module("app.website"));
var controller,scope,parentscope;
var $scope,$controller,$parentScope,$parentcontroller,$mdToast;
window.ga = function() {};
beforeEach(module("ngMaterial"));
beforeEach(module("ngAnimate"));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('SigninController',{$scope : scope});
}));
describe('simple program', function () {
it('should say true equals true', function () {
expect(scope.isShowProgress).toEqual(false);
});
});
});
TypeError: $scope.setMetaInfo is not a function
at init (public_html/components/myaccount/signin/controllers/signin_controller.js:77:20)
at new (public_html/components/myaccount/signin/controllers/signin_controller.js:65:9)
at Object.instantiate (public_html/bower_components/angular/angular.js:5055:14)
at $controller (public_html/bower_components/angular/angular.js:10975:28)
at public_html/bower_components/angular-mocks/angular-mocks.js:2308:14
at Object. (Unit%20Tests/demotest.js:17:22)
at Object.invoke (public_html/bower_components/angular/angular.js:5040:19)
at Object.WorkFn (public_html/bower_components/angular-mocks/angular-mocks.js:3182:20)
Error: Declaration Location
at window.inject.angular.mock.inject (public_html/bower_components/angular-mocks/angular-mocks.js:3145:25)
at Suite. (Unit%20Tests/demotest.js:15:16)
at Unit%20Tests/demotest.js:7:1
Chrome 59.0.3071 (Mac OS X 10.11.6): Executed 1 of 1 (1 FAILED) (0 secs / 0.204 secs)
Chrome 59.0.3071 (Mac OS X 10.11.6): Executed 1 of 1 (1 FAILED) ERROR (0.221 secs / 0.204 secs)
var MainTodayCtrl,
rootScope,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
rootScope = $rootScope.$new(); // root for parent controller
$controller('MainCtrl', { $scope: rootScope });
// then your MainCtrl will alter that scope
// for the child controller you need a "derived" scope
scope = rootScope.$new();
// now business as usua
MainTodayCtrl = $controller('MainTodayCtrl', {
$scope: scope
// place here mocked dependencies
});
}));
I have a directive that uses a service function like so:
angular.module('testModule',
['serviceBeingUsed'])
.directive('testDirective', function(serviceBeingUsed) {
return {
restrict: 'AE',
templateUrl: 'testTemplate.tpl.html',
scope: {
boundVar1: "="
},
link: function(scope) {
scope.getRequiredData = function(data){
//gether data using service
serviceBeingUsed.fetchRequiredData(data).then(
function(result){
scope.requiredData = result;
}
);
};
}
};
});
In the above directive I inject the service I wish to use and this service function gets used within the scope function "getRequiredData()" which is inside the "link" of this directive.
I have my test suite set up like so:
describe('test suite', function () {
var scope,
$rootScope,
$compile,
$q,
element,
isoScope,
serviceBeingUsed;
beforeEach(module('testModule'));
beforeEach( inject( function(_$rootScope_,
_$q_,
_$compile_,
_serviceBeingUsed_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
serviceBeingUsed = _serviceBeingUsed_;
$q = _$q_;
//This is where we create the directive and it's options.
element = angular.element('<test-directive bound-var1="blabla"></test-directive>');
//We create a new scope from the rootScope.
scope = $rootScope.$new();
//Now we compile the HTML with the rootscope
$compile(element)(scope);
//digest the changes
scope.$digest();
//We retrieve the isolated scope scope of the directive
isoScope = element.isolateScope();
}));
Now I have a test which runs and passes wherby I can spyOn the isolated scope function "getRequiredData()", this test looks like so:
it('getRequiredData runs', inject(function () {
spyOn(isoScope,"getRequiredData");
isoScope.getRequiredData();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
This proves that the link functions CAN be tested however when trying to test if the service function is called the test fails and I have no idea why, the test for the service looks like this:
it('serviceFunction runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
isoScope.getRequiredData();
expect(serviceBeingUsed.serviceFunction).toHaveBeenCalled();
}));
How can I successfully test if the service function has been called here?
In writing this example I have solved my issue. In my actual code, inside the test "serviceFunction runs" I had also included a spyOn(isoScope,"getRequiredData)"
This has the effect of blocking the inner functionality of the function
getRequiredData()
which meant the the service function inside getRequiredData could never run.
To resolve this issue I needed to edit the spy for the outer function
from:
spyOn(isoScope,"getRequiredData");
to:
spyOn(isoScope,"getRequiredData").and.callThrough();
this simple change means that the function being spied on will also run its inner code and not just register that it has been called.
However one important lesson that I have learned is to not do too much inside each test and to separate the tests as much as possible.
So just to clarify, my original test which failed looked like this:
it('getRequiredData runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
spyOn(isoScope,"getRequiredData");
isoScope.getRequiredData();
expect(serviceBeingUsed.fetchRequiredData).toHaveBeenCalled();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
the fix for this test which passes:
it('getRequiredData runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
spyOn(isoScope,"getRequiredData").and.callThrough();
isoScope.getRequiredData();
expect(serviceBeingUsed.fetchRequiredData).toHaveBeenCalled();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
I am trying to use AngularJS with RequireJS currently, but I do not know how to make the test work with injection.
Without RequireJS we could,
Impl
PhoneListCtrl.$inject = ['$scope', '$http'];
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) {
/* constructor body */
}];
Test
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
}));
However, when we use RequireJS we may define the controller as following,
demoController.js
define(["dependency"], function() {
/* constructor body */
});
When using this controller, we add it as one of the dependencies and do not have a variable declaration.(Let me just use "Controller" as an example since we'd better call it "Service")
someJS.js
define(["demoController"], function(controller) {
controller.method();
});
My Question
How can we inject the $http, $scope(or something else) to the target controller or service for testing when using RequireJS(AMD)?
Any help would be highly appreciated.
I've done something similar:
/*global define, document */
define(['angular', 'jquery'], function (angular, $) {
'use strict';
return function () {
var $injector = angular.bootstrap(document, ['myApp']);
var $controller = $injector.get('$controller');
var myController = $controller('myController');
};
});
The idea is that angular.bootstrap returns an injector, which allows you to fetch a service.
I finally made it work by following.
angular.module('app').controller('MyController', ['$scope', 'dep2', function ($scope, dep2) {
$scope.method = function () {//do something};
}]);
We can use this controller in test cases like this:
inject(function($controller, $rootScope, dep2) {
scope = $rootScope.$new();
myController = $controller("MyController",
{
$scope : scope,
dep2: dep2
});
);