I am the developer of this Vue.js Plugin and am currently working on the test for v1.0.0, using already written tests for older versions with some adjustments.
Scenario
Test the components with the following structure:
// receives props and passes through
VueEllipseProgress
// receives props, adds new and passes through
EpCircleContainer
// receives props and do main SVG rendering
CircleProgress
Use Factory function:
// this is the top level VueEllipseProgress component
import Container from "../../../src/components/VueEllipseProgress.vue";
import Circle from "../../../src/components/Circle/CircleProgress.vue";
const factory = propsData => {
return mount(Container, {
propsData: {
...propsData
}
});
};
const wrapper = factory({...})
Use wrapper.setProps() in the test to apply new props. Test, how changed props affect the rendering of SVG elements on the other end. You can see the whole code on GitHub.
Problem
wrapper.setProps() updates the props of the VueEllipseProgress (top level) correctly and wrapper.vm.$props has the expected values. But the props of CircleProgress component remain unchanged, HTML still not updated. This leads to test failures.
it("do some test", () => {
/* do some test here, all is fine */
wrapper.setProps({ someProp}); // set new props
wrapper.vm.someProp; // updated
circleWrapper.vm.someProp; // still old
// fails!!!
expect(circleWrapper.element.getAttribute("someProp")).to.equal(someProp);
});
Here are more code details related to above example.
Note, that the plugin works correctly live and all props are reactive.
The test worked for earlier versions of my plugin. In the meantime i have updated #vue/cli to version 4.x.x. Maybe the failures are related to this update, but I couldn't find any information in the release notes that could confirm this.
This is not the direct solution, more a tip to avoid the problem. In my components, I use v-binde="$props" to propagate the props to subcomponents. However, this can cause issues with jsdom, like in my case.
I ended up with refactoring all my tests by testing each komponent directly, avoiding the need of nested structure and props propagation (like unit tests are supposed to be).
Related
I'm having an exciting upgrade in a vue2 project using #vue/composition-api 0.6.7, trying to upgrade to 1.7.1.
Some tests are breaking and I'm noticing a pattern where the tests in question mount a component with the data parameter, as if they're reaching in and manipulating a ref
Example
// MyComponent.vue
<script>
export default defineComponent({
setup() {
const showModal = ref(false)
return {showModal}
}
})
</script>
Example test which was working before but now is broken.
it('should show the modal when the showModal ref is true', () => {
const wrapper = mount(MyComponent, {
data: {
showModal: true
}
});
expect(wrapper.find('#modal').exists()).toBe(true)
}
This makes sense to me that this broke in some ways because data is more of an options api thing and creating a ref is more of a composition-api thing-- probably more solidified as we went to 1.0 in the composition-api. That said, do you think that it should work?
When I rewrite the tests to NOT mount using the data prop and test it another way, the tests pass fine. I was expecting it to work as before and honestly not testing using the mount({data}), wrapper.setData(), or wrapper.vm seems like a better approach. I'm just looking for a confirmation or root cause why it worked before and not now.
Beyond this condensed code sample, I have tests using wrapper.setData({ serverResponse }) to simulate when a network call returns. This similarly breaks when I upgrade this composition-api package.
React doesn't provide an API that lets you pass in context to a created component class, so you have to write a wrapper component that provides the context.
Unfortunately, once you do this, you no longer have direct access to the component you are trying to test - unlike TestUtils.renderIntoDocument, functions like TestUtils.findRenderedComponentWithType don't return the actual rendered component instance, they only return the component constructor. Thus you can't call methods on the component, or set the component state to some known value before executing the test. The only thing you really have access to is the DOM node for your component, which is fine if all you want to do is black box testing, but for some kinds of components that's not sufficient.
I'm curious to know if anyone has come up with a solution for this. I've tried about a dozen different approaches, none of which work. (For example, I tried using 'ref' in my wrapper component, but it has the same problem - doesn't give you access to the real object.)
(Answering my own question)
Turns out the correct answer to all of this is to use enzyme, which replaces the standard React test utils - it offers a ton of features with a jQuery-like API, and best of all it completely supports component contexts. I've switched all of my tests over to it and it's great!
You can build a mock parent component like:
class MockContextContainer extends Component {
static propTypes = {
children: PropTypes.element.isRequired,
};
static childContextTypes = {
onSetTitle: PropTypes.func,
};
getChildContext() {
return {
onSetTitle: () => {},
};
}
render() {
return this.props.children;
}
}
Then use it in your test (in this case its a forgot password form example):
const cxtForgot = TestUtils.renderIntoDocument(
<MockContextContainer><ForgotPasswordForm /></MockContextContainer>
);
Which is what you may already be doing.
You can then do things like:
const input = TestUtils.findRenderedDOMComponentWithClass(
cxtForgot, 'ForgotPasswordForm-input'
);
// enter a valid email
input.value = 'abc#hotmail.com';
TestUtils.Simulate.change(input);
// no error class and button is now enabled
assert(!input.classList.contains('error'));
const button1 = TestUtils.findRenderedDOMComponentWithClass(
cxtForgot, 'primary-button'
);
assert(!button1.disabled);
the Simulate.change above can change the internal state of the component.
As for you question: "set the component state to some known value before executing the test", you can pass in different props to the component and have different tests for each scenario
I am using Ember with formatjs to internationalize my application, and ember-cli to build it all.
When I generate a component with
ember g component some-component
Ember also creates a test that checks that the component renders. However, if I use the intl-get helper from formatjs in the component template, the unit test fails.
So how can I register the custom helpers that formatjs creates for a unit test?
I first tried to add the intl-get helper:
moduleForComponent('some-component', {
needs: ['helper:intl-get']
});
However, this just fails inside intl-get when it tries to access "intl:main". I would like for the intl initializer to run, but I am not sure if there is even application setup. Or is it some way to just mock those methods using sinon?
My current workaround is to just delete the 'it renders' tests. But I would like for these tests to pass as well, so I can further test rendering later if I want.
Try:
moduleForComponent('some-component', {
integration: true
});
I am completely new to D3JS and would like to understand the testing strategies for D3 JS.
To elaborate little more on question - consider I have a simple page that shows a line graph using a TSV file.
Java Script Code:
function LineManager() {}
function LineProperties() { // Line Properties }
LineManager.prototype.draw = function(properties) {
// D3 code to draw a line with the given properties.
}
I am not able to think of test cases to be considered for writing unit tests. Here is a sample test that I wrote ..
it("should throw an exception if line graph properties are not set.", function() {
expect(lineManager.draw.bind(lineManager)).toThrow("Line Graph properties not set");
});
it("It should have single line chart", function() {
lineManager.draw(properties);
expect(lineManager.countLines()).toEqual(1);
});
I have written unit tests to make sure the TSV file is getting generated correctly. But does it make sense to write a unit test to see if the data is getting rendered correctly? Isn't that more of a d3js unit test rather than unit test for my function?
So my question is - what tests should be considered for charts generated by d3js?
I think I got the answer to my own question. Will try to explain it here.
It is not possible to validate whether the graph is plotted correctly by JS function written using D3JS. For this we may have to use Phantom.js or similar framework as mentioned by Chrisopher. I was not worried about making sure D3JS is plotting graph correctly, as any ways it is D3JS functionality and my code can safely assume D3JS is doing its work.
My worry is more of whether the data passed to D3JS is correct and as per my requirement. It is very much possible to make sure the properties of the graph are set correctly by creating Spy objects. I am providing a sample unit test covering test cases for a JS code plotting a Circle using D3JS.
CircleManager.js
function CircleManager() {};
CircleManager.prototype.draw = function(radius) {
var svg = d3.select("body")
.append("svg");
svg.attr("width", 100)
.attr("height", 100);
var circle = svg.append("circle");
circle.style("stroke", "black")
.style("fill", "white")
.attr("r", radius)
.attr("cx", 50)
.attr("cy", 50);
};
CircleManagerSpec.js
describe("draw", function() {
it("Constructs an svg", function() {
var d3SpyObject = jasmine.createSpyObj(d3, ['append', 'attr']);
// Returns d3SpyObject when d3.select method is called
spyOn(d3, 'select').andReturn(d3SpyObject);
var svgSpyObject = jasmine.createSpyObj('svg', ['append', 'attr', 'style']);
// Returns svgSpyObject when d3.select.append is called.
d3SpyObject.append.andReturn(svgSpyObject);
d3SpyObject.attr.andCallFake(function(key, value) {
return this;
});
svgSpyObject.append.andReturn(svgSpyObject);
svgSpyObject.attr.andCallFake(function(key, value) {
return this;
});
svgSpyObject.style.andCallFake(function(key, value) {
return this;
});
var circleManager = new CircleManager();
circleManager.draw(50);
expect(d3.select).toHaveBeenCalledWith('body');
expect(d3SpyObject.append).toHaveBeenCalledWith('svg');
expect(svgSpyObject.attr).toHaveBeenCalledWith('r', 50);
expect(svgSpyObject.attr).toHaveBeenCalledWith('width', 100);
expect(svgSpyObject.attr).toHaveBeenCalledWith('height', 100);
expect(svgSpyObject.style).toHaveBeenCalledWith('stroke', 'black');
expect(svgSpyObject.style).toHaveBeenCalledWith('fill', 'white');
});
});
Hope this helps.
I think you should consider this: http://busypeoples.github.io/post/testing-d3-with-jasmine/
And it really seems to make sense. I have read the others' answers but I am little bit disagree with them. I think we not only check if right function is called or not but we can check much more than that. Checking only some function call are good at unit testing level but not enough. Such test cases written by developer will be based on developer's understanding like these functions are called or not. But whether these methods should be called or not, this thing can be only checked by going at another level because unlike other work, here are code is making something not returning something that can be just checked and make sure everything is correct.
We obviously don't need to check whether D3 is doing its work correctly or not. So we can use D3 inside our testing code. But D3 renders SVG and we can check things like if svg have elements where expected. Again it is not going to test whether SVG is showing and rendering properly or not. We are going to check if SVG have elements which are expected and they are set as expected.
For example:
If this is bar chart, we can check the number of bars. As in example in above link here it is check that.
// extend beforeEach to load the correct data...
beforeEach(function() {
var testData = [{ date: '2014-01', value: 100}, { date: '2014-02', value: 140}, {date: '2014-03', value: 215}];
c = barChart();
c.setData(testData);
c.render();
});
describe('create bars' ,function() {
it('should render the correct number of bars', function() {
expect(getBars().length).toBe(3);
});
it('should render the bars with correct height', function() {
expect(d3.select(getBars()[0]).attr('height')).toBeCloseTo(420);
});
it('should render the bars with correct x', function() {
expect(d3.select(getBars()[0]).attr('x')).toBeCloseTo(9);
});
it('should render the bars with correct y', function() {
expect(d3.select(getBars()[0]).attr('y')).toBeCloseTo(0);
});
});
// added a simple helper method for finding the bars..
function getBars() {
return d3.selectAll('rect.bar')[0];
}
Some people probably gonna say that we are going to use D3 inside testing code? Again we should remember that purpose of test writing here is not to test D3 but our logic and SVG code that is compiled in response to our code.
This is just a way and jasmine is something that is helping us in writing test, you can also go into more detail and in different scenarios. You can make domain and check datapoints width height to cross check if they result into data which were given to render.
I think I am clear if not then check this link : http://busypeoples.github.io/post/testing-d3-with-jasmine/
Here write of this article have explained things in detail with how you can use jasmine.
Also I think I am still gone into detail. If only unit testing is required at different js functions level then there are a lot more things which can be tested without going into elements detail.
Testing strategy
The strategy I end up using to test d3.js code is to create helper functions to manage my data and settings. I then unit test these functions. So for charts I would check every functionality dealing with data, every function to set width, legends etc...
Concerning drawing functions it can get trickier but with testing frameworks such as buster.js it can be quite easy to implement these too. A good way of testing a chart would be to count the number of bars/lines in the page, check that legends are printing etc.
I would not try to check that the chart is visually the same because visually checking that the end result is the same is easiest. However, when writing the drawing functions, one should be very attentive to what happens on updates (will changing the data draw twice as many lines? are selectors right? ...)
Javascript testing
A great book on javascript testing is: Test Driven Javascript Development. It provides lots of examples and strategies to test javascript code. Most of them can be directly applied to d3.js code.
Tools
I recently looked for solutions for unit testing d3.js code and I ended up using the following tools:
buster.js
Buster.js is a very complete framework for unit testing javascript code in multiple browsers.
phantom.js
Phantom.js is a headless WebKit scriptable with a JavaScript API.
This means that it makes it easy to run automated tests on javascript without needing to use browsers such as chrome, safari etc..
EDIT: I would now use jasmine for unit testing and selenium (through the Saucelabs service maybe) for end to end testing.
Probably worth mentioning Jest snapshot testing. Jest/snapshots are popular for React UI components, as they're also difficult to test. With D3 you could generate an SVG take a snapshot and verify as your codebase evolves that you continue to generate the same output.
function makeChart(id, dataset) {
const chart = d3.select(id)
.append("svg")
.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "black")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 20);
return chart;
}
it('renders correctly', () => {
const myChart = makeChart('#example', [1,2,3])
expect(myChart.node()).toMatchSnapshot();
});
This test is untested
A snapshot can be any output a string "Foo Bar" a JSON object {foo: "bar"} etc.
Basically you have to run a test once that contains .toMatchSnapshot. Jest will then manage the generation and store a copy that it'll compare in future tests.
The concept is similar to VCR in Ruby testing. Record and replay.
I am working on converting a Backbone application into an Ember application using Ember Data. It works fine in the browser but the Jasmine test cases will not pass. When I try to create a record in the Jasmine test case I get this error:
TypeError: 'undefined' is not a function (evaluating 'type._create({ store: this })') in http://localhost:8888/spec/javascripts/generated/assets/application.js (line 26874)
This is the actual code that the error message points to:
createRecord: function(type, properties, transaction) {
properties = properties || {};
// Create a new instance of the model `type` and put it
// into the specified `transaction`. If no transaction is
// specified, the default transaction will be used.
//
// NOTE: A `transaction` is specified when the
// `transaction.createRecord` API is used.
var record = type._create({
store: this // line 26874
});
The actual code that the test case is executing looks like this:
nutrient = App.Nutrient.createRecord({"name_min":"nut 1","female_31_50_min":7.5,"male_31_50_min":8.0,"created_at":"2011-10-10T01:31:53Z","female_51_70_min":8.5,"updated_at":"2011-10-12T12:28:35Z","male_70_plus_min":10.0,"female_19_30_min":6.5,"child_4_8_min":4.0,"male_19_30_min":7.0,"lactating_14_18_min":5.75,"infant_0_05_min":1.0,"female_70_plus_min":9.5,"pregnant_14_18_min":5.8,"infant_6_12_min":2.0,"id":1,"male_9_13_min":5.0,"child_1_3_min":3.0,"female_9_13_min":4.5,"female_14_18_min":5.5,"male_14_18_min":6.0,"lactating_31_50_min":7.75,"pregnant_31_50_min":7.8,"pregnant_19_30_min":6.8,"male_51_70_min":9.0,"lactating_19_30_min":6.75,"female_31_50_max":8.5,"male_31_50_max":9.0,"female_51_70_max":9.5,"male_70_plus_max":11.0,"female_19_30_max":7.5,"child_4_8_max":5.0,"male_19_30_max":8.0,"lactating_14_18_max":6.75,"infant_0_05_max":2.0,"female_70_plus_max":10.5,"pregnant_14_18_max":6.8,"infant_6_12_max":3.0,"male_9_13_max":6.0,"child_1_3_max":4.0,"female_9_13_max":5.5,"female_14_18_max":6.5,"male_14_18_max":7.0,"lactating_31_50_max":8.75,"pregnant_31_50_max":9.8,"pregnant_19_30_max":7.8,"male_51_70_max":10.0,"lactating_19_30_max":7.75})
person = new App.Person.createRecord({age: 0.25})
expect(nutrient.requiredNutrientForPerson(person)).toEqual({min_amount: 1.0, max_amount: 2.0})
Any ideas would be appreciate.
In general, if you're having problems with a test that you don't experience in the browser, it's because the tests are running outside of the Ember run loop.
Try calling Ember.run.sync() before expect() to force synchronization. Alternatively, place any code that involves binding in an anonymous fn inside: Ember.run(function() { }).
Check out the ember and ember-data source for other testing examples, since coverage is pretty solid.
With that said, I'm not an ember-data expert, so I'm not sure if this is the problem you're experiencing.
Sorry, My Bad. The problem is with this line:
person = new App.Person.createRecord({age: 0.25})
I needed to remove the new keyword and it worked correctly