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

ProjectStatus unit tests #4127

Merged
merged 9 commits into from
Nov 8, 2017
Merged

ProjectStatus unit tests #4127

merged 9 commits into from
Nov 8, 2017

Conversation

jelliotartz
Copy link
Contributor

Describe your changes

  • Adds tests for the ProjectStatus component in Admin.
  • Tests are divided into a group that checks the presence of the basic page layout elements, and a group that focuses on the Workflow Settings section (where I've recently implemented changes regarding toggling active status of default workflows)
    • RFC: Not sure if testing page layout elements is necessary, or a good practice. Happy to remove if not needed/wanted.
  • RFC: So far I haven't been able to write tests that simulate a user clicking the WorkflowToggle checkbox, getting the WorkflowDefaultDialog modal to open, and testing its functionality.
  • Related PRs (my recent work related to ProjectStatus component and toggling default workflow status):

Review Checklist

  • Does it work in all major browsers: Firefox, Chrome, Edge, Safari?
  • Does it work on mobile?
  • Can you rm -rf node_modules/ && npm install and app works as expected?
  • Did you deploy a staging branch?
  • If the component is in coffeescript, is it converted to ES6? Is it free of eslint errors? Is the conversion its own commit?
  • Are the tests passing locally and on Travis?

Optional

Copy link
Contributor

@srallen srallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if testing page layout elements is necessary, or a good practice. Happy to remove if not needed/wanted.

No, we do not need to re-test what React and JSX is doing for us for standard HTML elements. We trust that our dependencies are tested and we should not spend time re-testing core functionality of them. We do we want to test that our custom React components are rendered.

So far I haven't been able to write tests that simulate a user clicking the WorkflowToggle checkbox, getting the WorkflowDefaultDialog modal to open, and testing its functionality.

Testing the interaction between components are integration tests. Just focus on writing unit tests for this component. The dialog component already has its own unit tests written, too.

Let me know if you need further clarification on what should be covered.

@srallen srallen self-assigned this Nov 1, 2017
@jelliotartz
Copy link
Contributor Author

jelliotartz commented Nov 1, 2017

PR Update

Thanks for your input @srallen :) RFC: I was writing a test for rendering the WorkflowDefaultDialog component, and came upon an issue - the component is currently rendered once for every workflow, rather than just once, for the default workflow. (you can test this by inspecting the modal element on a project with multiple workflows).

This seems like an issue that should be resolved, and I've refactored the code in ProjectStatus to only render one WorkflowDefaultDialog component when the modal is triggered (on a separate branch, not currently included here). However, updating ProjectStatus to only render one WorkflowDefaultDialog seems like it's outside of the scope of this PR. But, the issue was discovered in the process of writing the test for the presence of the component, which is within the scope of the PR...

I pushed up changes that include a passing test for the WorkflowDefaultDialog component (it tests whether it renders one for each workflow), with a comment to refactor ProjectStatus so that we only render one component. Happy to either update ProjectStatus and the test within this PR, or to open up a separate PR to address the issue and fix the test there, just wanted to confer before continuing :)

@srallen
Copy link
Contributor

srallen commented Nov 2, 2017

Let's get the fix in with this. Just keep the tests and the bug fix as separate commits.

this.setState({
dialogIsOpen: true
});
}

if ((defaultWorkflowId !== workflow.id) || (defaultWorkflowId === workflow.id && !workflow.active)) {
if ((this.state.defaultWorkflowId !== workflow.id) || (this.state.defaultWorkflowId === workflow.id && !workflow.active)) {
Copy link
Contributor Author

@jelliotartz jelliotartz Nov 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I was updating this conditional, I was wondering if it's necessary - I tried changing it to just be an else attached to the conditional above it, and the UI functionality seems to be the same (tests all still passing as well).

Would appreciate input on whether it's necessary here to explicitly check for the opposite of the conditions described above.

@jelliotartz
Copy link
Contributor Author

PR Update

  • Bug fix for modal added on a separate commit.
  • I've added unit tests for the custom components, which I put in their own describe block.
  • I created a 'workflow settings' describe block, where I unit test each of the custom components associated with workflow settings, as well as the #onChangeWorkflowLevel method.
  • I'm not sure if I need to write tests for other methods in this component, (e.g., project and workflow GET requests, componentDidMount, and the event handler methods). Please let me know if more tests need to be written to cover unit testing for this component :)

Copy link
Contributor

@srallen srallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I need to write tests for other methods in this component, (e.g., project and workflow GET requests, componentDidMount, and the event handler methods).

  • Event handlers should be unit tested with stubs, mocks, or spies.
  • componentDidMount depends on the component. Shallow rendering doesn't fire this lifecycle method. In this case cdm is calling a method that is requesting data from the API
  • Testing the interaction with the API are integration tests, so for this PR, you do not need to worry about that.

For the bug, the reason multiple dialogs were being rendered is it is in the map. I didn't catch that before in the last PR. Recall it was originally placed there to bind the individual workflow ids to the click hander, but that's not being done anymore. Instead of redundantly setting the state with the default workflow id, pull the rendering of the dialog outside of the map of the workflows.

@jelliotartz
Copy link
Contributor Author

jelliotartz commented Nov 6, 2017

PR Update

  • Added unit tests for all of the event handlers, except for handleToggle
  • RFC: I've been stuck on how to test the call of handleToggle for a while now - pretty sure I'm missing something in the syntax, but can't figure it out.
    • I'm getting AssertError: expected handleToggle to be called once but was called 0 times
    • My hunch is that the issue is a syntax error here, but I could be off base on that.
    • Additionally, this event handler is different in that it's an event that is passed to a custom component, which then calls it via an onChange event on its input - I've been reading links from Sinon's docs for how to stub complex objects, but so far no luck applying their solutions to this scenario.
    • Would appreciate suggestions on how to test the handleToggle event handler :)

Copy link
Contributor

@srallen srallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the failing test, check out enzyme's docs on simulate. The second gotcha gives a clue:

Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the event you give it. For example, .simulate('click') will actually get the onClick prop and call it.

The test is currently finding the last shallow rendered WorkflowToggle and attempting to simulate the click event, which as enzyme describes, finds the onClick prop and calls it. WorkflowToggle doesn't have an onClick prop. The prop handleToggle is passed down to the child which does have an onClick prop. So, this event should be unit tested on WorkflowToggle. Testing the prop passing down to WorkflowToggle and that the class method handleToggle is called would be an integration test.

Also, just an FYI, the event variable isn't defined, so even if you were going to write an integration test, an undefined variable is being passed into the simulated event.


wrapper = shallow(<ProjectStatus />);

wrapper.setState({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to happen in the before. You're setting state in your tests already for the specific condition that's being tested.


describe('custom components', () => {
it('LoadingIndicator renders if project is not in state', () => {
wrapper.setState({ project: null });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to happen once you remove the setState out of the before block. You do want to test what is rendered for the initial state. There should also be a unit test for when the workflows state is null (the initial state for workflows)

assert.equal(loadingIndicator.length, 1);
});

it('LoadingIndicator does not render if project is in state', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps put the unit tests that rely upon there being a project in the component state in its own describe block.

onChangeWorkflowLevelSpy = sinon.stub(ProjectStatus.prototype, 'onChangeWorkflowLevel');
handleDialogCancelSpy = sinon.stub(ProjectStatus.prototype, 'handleDialogCancel');
handleDialogSuccessSpy = sinon.stub(ProjectStatus.prototype, 'handleDialogSuccess');
handleToggleSpy = sinon.stub(ProjectStatus.prototype, 'handleToggle');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be consistent with variable names. If they're stubs, then name the variables stubs.

@jelliotartz
Copy link
Contributor Author

Thanks, yeah, I was thinking perhaps the issue was actually that I was still getting integration and unit tests mixed up - thanks for clearing that up.

Tests now refactored into describe blocks that set state in a rational way - start with the initial null states for project and workflow, test for loading indicator, then add the project mock object, test what should/shouldn't be there, then add workflows, and appropriate unit tests.

@jelliotartz jelliotartz changed the title [WIP/RFC]: ProjectStatus unit tests ProjectStatus unit tests Nov 7, 2017
const versionListComponent = wrapper.find('VersionList');
assert.equal(versionListComponent.length, 1);
});

it('renders a no workflows found message when no workflow is in component state', () => {
const noWorkflowsMessage = wrapper.find('div[children="No workflows found"]');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to target this element using a solution suggested here, but, as one person comments, this seems tightly coupled to the implementation - would it be better to move this text to a Translate component, and then test whether what is rendered matches what is in the Translate component's content prop? (something more like this)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need translations here. I'm of the opinion that finding specific DOM elements for a unit test will always be coupled with the implementation. It's unavoidable, however, I would avoid using a prop that is internal to the React API as well as using a selector that looks like a CSS selector when it's not. Here's how I'd write this test and the DOM traversal:

      const noWorkflowsMessage = wrapper.find('.project-status__section').at(1).children().find('div');
      assert.equal(noWorkflowsMessage.text(), 'No workflows found');

It also better matches the description of the test, which is that it renders a specific message.

@jelliotartz
Copy link
Contributor Author

Thanks, I'll keep that in mind going forward :)

Just rebased this against master.

@srallen srallen merged commit d6ece84 into master Nov 8, 2017
@srallen srallen deleted the project-status-unit-tests branch November 8, 2017 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants