Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom jasmine matchers #5456

Closed
Foxandxss opened this issue Nov 24, 2015 · 17 comments
Closed

custom jasmine matchers #5456

Foxandxss opened this issue Nov 24, 2015 · 17 comments

Comments

@Foxandxss
Copy link
Member

So since Angular needs to wrap around Jasmine to do custom stuff, we are now stuck a bit when extending Jasmine.

Let's say I want to add a custom matcher:

jasmine.addMatchers({
  toHaveWidth: ...
});

Then when I want to use it, I get a:

error TS2339: Property 'toHaveWidth' does not exist on type 'NgMatchers'.

I guess that angular2/testing needs a way to register new matchers or perhaps 3rd party libraries needs to extend NgMatchers with custom matchers.

Is there any plan with this or what convention should the community spread on this one?

@WiktorKa
Copy link

+1

@skywalker5727
Copy link

Angular 2.
I got that error when I did not have the new matcher defined in my typings file. For a temporary workaround I added it the definition to jasmine.d.ts. I was able to get my custom matcher to work if it was in the same file as my tests. Now I am trying to work out how to create a single file to hold all custom matchers so that I can reuse them across tests, get it loaded into the browser, etc.

I am trying to copy the way the matchers are defined in testing.dev.js - they are wrapped in 'System.register("angular2/src/testing/matchers", ["angular2/src/platform/dom/dom_adapter", "angular2/src/facade/lang", "angular2/src/facade/collection"], true, function (require, exports, module) {...'
but if I used this I lose either my matcher or the ngMatchers, depending on which is first in karma.conf.js. I do not understand what is going on with the System.register, and I expect the issue it there - only the first one listed in karma.conf is usable (obviously, I've given them the same name, but if I make up a different name for my file, the matchers aren't found) - I don't know how to create a different System.register that would be used. (I'm not a programmer, I'm a tester)

I also tried loading in the karma-jasmine-matchers library from npm. You also need to add the matcher to your typings for those to work. I haven't yet tried working out how those matchers are getting into the browser to be used by the tests - that may be a pattern to follow for custom matchers.

Using either pattern, I'm not sure I'd be able to use Typescript. It would be nice if someone cleverer than I am would figure out a nice way to use custom matchers.

@skywalker5727
Copy link

It turned out to be straight-forward to get a custom matcher in another file and use it in my test - I had to go back to my Typescript notes.

This is a file that contains the custom matchers

function valuesToBeUniqueForProperty() {
    return {
        compare: function (actual: Array<any>, property: string) {
            let myMessage: string;
            let noDups: boolean = true;
            let propertyValues: Array<any> = [];

            function CheckForDuplicates(item) {
                if (propertyValues.indexOf(item[property]) != -1)
                { noDups = false };
                propertyValues.push(item[property]);
            }

            actual.forEach(CheckForDuplicates);

            var uniqueness = noDups ? " is unique - expected it to not be unique" : " is not unique - expected it to be unique";

            return {
                pass: noDups,
                message: 'Property ' + property + uniqueness
            }

        }
    }
}

export {valuesToBeUniqueForProperty}

This is in the spec file

import { valuesToBeUniqueForProperty } from './custom-matchers-3';

beforeEach(() => {
    jasmine.addMatchers({
        valuesToBeUniqueForProperty
    })
})

it('getHeroes() returns 10 values', injectAsync([HeroService], (heroServ) => {
    return heroServ.getHeroes().then((servHeroes) => {
        expect(servHeroes.length).toEqual(10);
        expect(servHeroes).valuesToBeUniqueForProperty('id');
    });

}))

Now I just need to figure out a custom typings file so I don't need to reference the custom matchers in the Jasmine one.

@wesleycho
Copy link
Contributor

Now that Angular does not wrap this any longer, I believe this can now be closed. The solution now is that the user has to type cast expect and extend jasmine.Matchers in their code in order to support custom matchers.

@rcomblen
Copy link

An example would be most welcome ...

@faminram
Copy link

I wonder the example mentioned by "skywalker5727 commented on Apr 19" not working for me..... I get the following compile time error

[ts] Property '....' does not exist on type 'Matchers'.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Nov 27, 2016

It is necessary to create a custom typing file with the content like following for a custom matcher 'toHaveSameSequences' and use it as the reference:

/// <reference path="../matchers/my.matcher.d.ts"/>

Of course together with "../matchers/my.matcher.ts" where the matcher "toHaveSameSequences" is defined and "import" it where necessary.

../matchers/my.matcher.d.ts:

declare module jasmine {
	interface Matchers {
		toHaveSameSequences(expected: any): boolean;
	}
}

../matchers/my.matcher.ts:

let customMatchers: jasmine.CustomMatcherFactories = {
	toHaveSameSequences: function (util: jasmine.MatchersUtil, customEqualityTesters: Array<jasmine.CustomEqualityTester>): jasmine.CustomMatcher {
		return {
			compare: function (actual: any, expected: any): jasmine.CustomMatcherResult {
				const result: jasmine.CustomMatcherResult = {
					pass: false,
					message: ''
				}
				for (let i=0; i<actual.length; i++) {
					if (actual[i] !== expected[i]) {
						result.message = 'Wrong sequence on #' + (i + 1);
						return result;
					}
				}
				result.pass = true;
				return result;
			}
		}
	}
};

export { customMatchers };

import { customMatchers } from '../matchers/my.matcher';
....
beforeEach(() => {
	jasmine.addMatchers(customMatchers);
});
....
it('custom matcher toHaveSameSequences', () => {
	expect([4,3]).toHaveSameSequences([4,3]);
});

@icfantv
Copy link

icfantv commented Dec 6, 2016

@mlc-mlapis in what file does that /// <reference... line go?

@mlc-mlapis
Copy link
Contributor

@icfantv In your *.spec.ts file where you write your tests and use a custom matcher:

/// <reference path="../matchers/my.matcher.d.ts"/>

import { HttpModule } from '@angular/http';
import { TestBed, async, inject } from '@angular/core/testing';

import { HelloService } from './hello.service';
import { customMatchers } from '../matchers/my.matcher';

describe('Hello Service', () => {

	beforeEach(() => {
		TestBed.configureTestingModule({
			imports: [HttpModule],
			providers: [
				HelloService
			]
		});

		jasmine.addMatchers(customMatchers);
	});

	it('custom matcher toHaveSameSequences', async(
		inject([HelloService], (_helloService) => {
			expect([4, 3]).toHaveSameSequences([4, 3]);
		})
	));

	it('_helloService to be defined', async(
		inject([HelloService], (_helloService) => {
			expect(_helloService).toBeDefined();
		})
	));
});

@icfantv
Copy link

icfantv commented Dec 6, 2016

@mlc-mlapis ok, thanks. that's what i ended up doing to get it working. is there no option to make it global? i'm only using it in one file for now...but that this solution doesn't scale.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Dec 7, 2016

@icfantv ... probably yes - it could be the same way as Angular 2 moved from "typings" and using

/// <reference path="../typings/browser.d.ts" />

to node modules like

"@types/core-js": "0.9.35"

but I did not try this way yet. If you try to look at it, let us know.

@icfantv
Copy link

icfantv commented Dec 7, 2016

I'm upgrading our project to at-types and adding the jasmine at-type (and to the types property in tsconfig) does not rectify the issue. The compiler still complains.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Dec 7, 2016

Hmm, there will be something ... probably it will be necessary to deeply look at details how at-types and TS cooperate. I am using at-types also.

@icfantv
Copy link

icfantv commented Dec 7, 2016

Agreed. I'm fairly certain this is a config issue, but it's not clear to me at this point what exactly the issue might be.

@icfantv
Copy link

icfantv commented Jan 18, 2017

We got this working as a temporary workaround with a custom d.ts file hosted locally in our repo because DT's UIBS typings file didn't have the interfaces exported in a nice, easily-importable manner. I submitted a PR to DT that was merged and they are now easily importable using ES6 module import syntax so we were able to remove this workaround, and the custom typings file, from our code.

@mlc-mlapis
Copy link
Contributor

@icfantv Oh, great news. 🥇 I will try that way as soon as possible. Thanks.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants