How do you use a Typescript Unit Test to test Typescript in Visual Studio? - visual-studio

I'm trying to write a unit test in Typescript to test a Typescript class, but when the test is run, it doesn't know anything about the class.
I'm using Typescript (1.4) with Node Tools for Visual Studio (2013) and the test helpfully appears in Test Explorer. When run it fails with "Reference Error: ClassC not defined."
The class I'm testing:
class ClassC {
functionF() {
return 42;
}
}
Generated Javascript:
var ClassC = (function () {
function ClassC() {
}
ClassC.prototype.functionF = function () {
return 42;
};
return ClassC;
})();
//# sourceMappingURL=ClassC.js.map
The test (created from template Add -> new Item... -> TypeScript UnitTest file):
/// <reference path="ClassC.ts" />
import assert = require('assert');
export function classCTest() {
var foo: ClassC = new ClassC();
var result: number = foo.functionF();
assert.equal(result, 42);
}
Generated Javascript:
var assert = require('assert');
function classCTest() {
var foo = new ClassC();
var result = foo.functionF();
assert.equal(result, 42);
}
exports.classCTest = classCTest;
//# sourceMappingURL=ClassC_tests.js.map
When looking at the generated Javascript for the test it becomes obvious why the error occurs. It does not contain the necessary definition for ClassC. I thought including the reference path would help, but it obviously didn't.
How do I get the unit test to know about the class?

I thought including the reference path would help, but it obviously didn't.
export class ClassC and then use an import statement instead of a reference comment. Also compile with the compiler flag --module commonjs.
More : https://www.youtube.com/watch?v=KDrWLMUY0R0&hd=1 and http://basarat.gitbooks.io/typescript/content/docs/project/external-modules.html

Related

Jasmine Unit testing define library that was imported as a global variable

I have a project that uses pdfMake to generate a PDF. To use it I include the file in my index.html
<script src='js/pdfmake.js'></script>
<script src='js/vfs_fonts.js'></script>
Inside pdfmake.js it declares global["pdfMake"] which then allows me to use the library in my service.
pdfService:
pdfMake.createPdf(docDefinition).download(fileName);
Everything works great but when I tried to test ths method in my service I get an error that the test can't find the variable pdfMake. That makes sense considering it's loaded by index.html.
How can I replace this library with a mock in my test?
I've tried using a spy but since makePdf isn't a function that doesn't work. spyOn(service, 'makePdf').
I tried just setting it as a variable but that also didn't work and I get: Strict mode forbids implicit creation of global property 'pdfMake'
pdfMake = {
createPdf: jasmine.createSpy('createPdf').and.returnValue({
download: jasmine.createSpy('download')
}
}
I got the same problem and solved inserting the pdfMake mock on global variable window inside the unit test. So, in your case will be something like this:
window.pdfMake = {
createPdf: jasmine.createSpy('createPdf')
.and.returnValue({
download: jasmine.createSpy('download')
}),
};
I just fixed this issue by making below changes-
Declare pdfMake variable globally in your .ts file like-
declare var pdfMake;
And then mock the pdfMake function in your .spec file like this-
window['pdfMake'] = {
createPdf: function (param) {
return {
open: function () {
return true;
},
download: function () {
return true;
}
};
}
};

Problems with defining modules using AMD in Mocha

While writing tests I got bug TypeError: $.extend is not a function on toastr plugin that we are using. It seems that jQuery is not picked up properly, even tho is working normally in browser.
In our main mock file we imported jQuery and bind it to global windows object and it's accessible across whole application (but toastr plugin), even while testing in mocha:
import jsdom from 'jsdom';
import $ from 'jquery';
import chai from 'chai';
import chaiImmutable from 'chai-immutable';
import React from 'react';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
const win = doc.defaultView;
global.document = doc;
global.window = win;
global.expect = chai.expect;
global.$ = $(win);
global.jquery = $(win);
global.jQuery = $(win);
Object.keys(window).forEach((key) => {
if (!(key in global)) {
global[key] = window[key];
}
});
chai.use(chaiImmutable);
So while taking closer look at toastr I noticed this:
; (function (define) {
define(['jquery'], function ($) {
// this function is called on inizialization
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
It takes jquery from node_modules directly and then defines object $ in function scope, that means that it's ignoring window.$ (which is working normally even in here).
Therefore logging $ will return function, and logging $.anyMethodFromjQuery ($.extend) will return undefined.
In the end I tried logging $.prototype, in the browser it will return me jQuery object while in mocha it returns empty object {}
So in the end it define didn't created prototype in mocha environment and I cannot add one line of code $ = window.$; in plugin, since no one should edit a plugin + we are using npm.
Is there any solution for this?
You're running into trouble because you are loading code that should be loaded by JSDom outside of it. While it is possible in some cases to load code in Node and then pass it to the window that JSDom creates, that's a brittle approach, as you've discovered. Whenever I use JSDom for things other than write-and-toss cases, I load every script that would normally be loaded by a browser through JSDom. This avoids running into the issue you ran into. Here's a proof-of-concept based on the code you've shown in the question. You'll see that toastr.getContainer() runs fine, because Toastr has been loaded by JSDom. If you try to use the same code with an import toastr from "toastr" you'll get the same problem you ran into.
import jsdom from 'jsdom';
import $ from 'jquery';
import path from "path";
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>', {
features: {
FetchExternalResources: ["script"],
ProcessExternalResources: ["script"]
}
});
const win = doc.defaultView;
global.document = doc;
global.window = win;
global.$ = $(win);
global.jquery = $(win);
global.jQuery = $(win);
window.addEventListener("error", function () {
console.log("ERROR");
});
const script = document.createElement("script");
script.src = path.join(__dirname, "./node_modules/toastr/toastr.js");
document.head.appendChild(script);
// The load will necessarily be asynchronous, so we have to wait for it.
script.addEventListener("load", function () {
console.log("LOADED");
console.log(window.toastr);
// We do this here so that `toastr` is also picked up.
Object.keys(window).forEach((key) => {
if (!(key in global)) {
global[key] = window[key];
}
});
toastr.getContainer();
});
Note that the code hung when I tried calling toastr.info(...). I took a look at the code of Toastr but it is not clear to me what causes the problem. There are features of browsers that JSDom is unable to emulate. It is possible that Toastr is dependent on such features. In the past, I've sometimes had to switch test suites away from JSDom due to its limitations.

TypeScript dynamic loading of AMD module ends in "Could not find symbol '...'

No, this topic won't answer my question and NO, the solution is not simply importing Command in the nav.ts file. nav.ts is one of many viewModel-files and they will be loaded dynamically on demand. The only problem is to set the parameter's type in the constructor of the class. (Type has to be "Command")
In the following class, which will be loaded by require.js, the method viewModel() requires a new class dynamically. In this case NavViewModel .
command.ts
export class Command {
...
public viewModel(name: string, callback: Function) {
require(["noext!boot/getViewModel/" + name], function (viewModel) {
callback(viewModel);
});
}
}
This is the class which will be fetched by viewModel():
nav.ts
export class NavViewModel extends kendo.Router {
constructor(command: Command) {
super();
this.route('/:name', function (name) {
command.view(name, $('div.content'));
});
this.start();
}
}
EDIT:
Here is the entry-point (requested in comment 2)
main.ts (EntryPoint)
import lib = require("command");
var cmd = new lib.Command();
cmd.viewModel('nav', function (o) {
cmd.view('nav', $('div.header'), function () {
kendo.bind($('.header .nav'), new o.NavViewModel(cmd));
});
});
/EDIT
The Problem:
Visual Studio will throw the error TS2095: Could not find symbol 'Command', because the "Command" class ist not defined in this Module.
The program works fine if the "Command"-Type will be removed from the NavViewModel constructor. Is there any solution to reference the Command class in the NavViewModel?
This won't work:
/// <reference path="../../Scripts/command.ts" />
When using RequireJS, the import statement should be the full path from the root of the application.
I also use a slightly different export syntax
command.ts
class command {
...
}
export = command;
main.ts
// I'm assuming the Scripts folder is at the root of the application
import Command = require('Scripts/command');
var cmd = new Command();
Note
I'm using Typescript 0.9.1.1. I can't upgrade my machine to 0.9.5 as a large internal application is affected by some breaking changes between versions

How can I check if project is a Test Project? (NUnit, MSTest, xUnit)

I want to check if selected project (I have source code) is a TestProject for one of the following framework: NUnit, MSTest, xUnit.
For MSTest it is simple. I can check .csproj and tag. If I have there {3AC096D0-A1C2-E12C-1390-A8335801FDAB} than it means it is Test project.
The problem are NUnit and xUnit. I can check for this cases references in .csproj. If I have nunit.framework or xunit it will be obvious. But I wondering if is possible to check this in diffrent way.
Do you know different way to recognize test projects?
One of the way is to check if assembly contains test methods. Attributes for test methods are as following:
NUnit: [Test]
MSTest: [TestMethod]
xUnit.net: [Fact]
Iterate over assemblies and check if assembly contains class with test methods. Example code:
bool IsAssemblyWithTests(Assembly assembly)
{
var testMethodTypes = new[]
{
typeof(Xunit.FactAttribute),
typeof(NUnit.Framework.TestAttribute),
typeof(Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute)
};
foreach (var type in assembly.GetTypes())
{
if (HasAttribute(type, testMethodTypes)) return true;
}
return false;
}
bool HasAttribute(Type type, IEnumerable<Type> testMethodTypes)
{
foreach (Type testMethodType in testMethodTypes)
{
if (type.GetMethods().Any(x => x.GetCustomAttributes(testMethodType, true).Any())) return true;
}
return false;
}
You can also add more assumptions:
check if classes contains TestFixture method,
check if classes / test methods are public.
EDIT:
If you need to use C# Parser, here is a sample of NRefactory code for checking if a .cs file contains classes with tests:
string[] testAttributes = new[]
{
"TestMethod", "TestMethodAttribute", // MSTest
"Fact", "FactAttribute", // Xunit
"Test", "TestAttribute", // NUnit
};
bool ContainsTests(IEnumerable<TypeDeclaration> typeDeclarations)
{
foreach (TypeDeclaration typeDeclaration in typeDeclarations)
{
foreach (EntityDeclaration method in typeDeclaration.Members.Where(x => x.EntityType == EntityType.Method))
{
foreach (AttributeSection attributeSection in method.Attributes)
{
foreach (Attribute atrribute in attributeSection.Attributes)
{
var typeStr = atrribute.Type.ToString();
if (testAttributes.Contains(typeStr)) return true;
}
}
}
}
return false;
}
Example of NRefactory .cs file parsing:
var stream = new StreamReader("Class1.cs").ReadToEnd();
var syntaxTree = new CSharpParser().Parse(stream);
IEnumerable<TypeDeclaration> classes = syntaxTree.DescendantsAndSelf.OfType<TypeDeclaration>();
I would look for usage of the attributes representing each framework to see which is which.
Use reflection to find classes/methods with the appropriate attribute types (e.g. Test/TestFixture)
This answer has an example that you can modify to meet your needs:
get all types in assembly with custom attribute
Check NuGet references in *.csproj for specific nuggets used for testing.

Is there a way to add a Jasmine matcher to the whole environment

There are plenty of documents that show how to add a matcher to a Jasmine spec (here, for example).
Has anyone found a way to add matchers to the whole environment; I'm wanting to create a set of useful matchers to be called by any and all tests, without copypasta all over my specs.
Currently working to reverse engineer the source, but would prefer a tried and true method, if one exists.
Sure, you just call beforeEach() without any spec scoping at all, and add matchers there.
This would globally add a toBeOfType matcher.
beforeEach(function() {
var matchers = {
toBeOfType: function(typeString) {
return typeof this.actual == typeString;
}
};
this.addMatchers(matchers);
});
describe('Thing', function() {
// matchers available here.
});
I've made a file named spec_helper.js full of things like custom matchers that I just need to load onto the page before I run the rest of the spec suite.
Here's one for jasmine 2.0+:
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return { pass: angular.equals(actual, expected) };
}
};
}
});
});
Note that this uses angular's angular.equals.
Edit: I didn't know it was an internal implementation that may be subjected to change. Use at your own risk.
jasmine.Expectation.addCoreMatchers(matchers)
Based on previous answers, I created the following setup for angular-cli. I also need an external module in my matcher (in this case moment.js)
Note In this example I added an equalityTester, but it should work with a customer matcher
Create a file src/spec_helper.ts with the following contents:
// Import module
import { Moment } from 'moment';
export function initSpecHelper() {
beforeEach(() => {
// Add your matcher
jasmine.addCustomEqualityTester((a: Moment, b: Moment) => {
if (typeof a.isSame === 'function') {
return a.isSame(b);
}
});
});
}
Then, in src/test.ts import the initSpecHelper() function add execute it. I placed it before Angular's TestBed init, wich seems to work just fine.
import { initSpecHelper } from './spec_helper';
//...
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// Init our own spec helper
initSpecHelper();
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
//...

Resources