question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How should I unit test a rule?

See original GitHub issue

Question about GraphQL Shield

I’m unit testing my rules and I managed to do it like this but it doesn’t feels right, is very verbose and I have to use “any” on typescript so types don’t fail.

  it("is valid when a valid token is provided", async () => {
    const context = {
      req: jest.fn()
    }

    const isValid = await isAuthenticated.resolve(
      null, // parent
      null, // args
      context, // Context
      null, // Info
      {} as any, // Options
    );

    expect(isValid).toEqual(true);
  });

Like this resolve does nothing 😕

What approach you will use?

  • [x ] I have checked other questions and found none that matches mine.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:8
  • Comments:8

github_iconTop GitHub Comments

2reactions
IsaaXcommented, Feb 1, 2019

I’ve been fairly interested in graphql-shield and writing specs that is specific to my codebase. So I’ll share on how I’ve been designing my unit tests in order to get confidence in my app without necessarily having to unit test graphql-shield. This library is solid and rather than writing tests in your own library validating the library works as its advertised, all tests related to graphql-shield logic should lie inside this repo. As for in your codebase, you should write specs more geared to the rules you write so that should allow you to feel confident in reusing your rules. I would manually just validate that the caching works completely fine (unless someone has an easy to implement spec 😃 )

So I will be speaking around the topic of resolver level rules, and not field specific rules. Right now we are currently still developing so I haven’t had to face that challenge yet.

Because i’m not doing any field specific rules, I’ve created helper functions that assert whether the permission passed or did not. (Note: I’m using mocha/chai for my unit tests) The premise of these specs is, if there’s anything in the “data” key, then assume it worked and expect no errors. For validating failing tests, assert the exact error. Just to note, the validateSuccessfulRequest expects your the resolvers inputs/payloads to be required in the schema. resolver(input: ResolverInput!): ResolverPayload!

/*
 PERMISSION GRANTED SPECS
*/

const validateSuccessfulRequest = (res, operationName, debug = false) => {
  if (debug || res.body.errors) {
    res.body.should.be.eql({});
    // sometimes on errors, the body doesn't get set so we check text as well
    res.text.should.be.eql({});
  }
  res.status.should.be.eql(200);
  // since we are doing permission specs, we do assertions on if there is data available and no errors. We don't care
  // about what's inside, this is just to check that if it is present, then the user does/does not have access.
  res.body.should.not.have.property('errors');
  res.body.should.have.property('data');
  res.body.should.have.nested.property(`data.${operationName}`).that.should.not.be.null;
};

/*
* PERMISSION DENIED SPECS
* */

export const validateRejectedRequest = (res, operationName, nullable, debug = false) => {
  if (debug || res.status === 400) {
    // In order to get better visibility of what our error is, we compare it
    // to an empty obj to deep display what our payload is
    res.body.should.be.eql({});
  }

  if (nullable) {
    res.body.should.have.nested.property(`data.${operationName}`, null);
  } else {
    res.body.should.have.nested.property(`data`, null);
  }

  res.body.should.have.property('errors').that.deep.equals([
    {
      locations: [],
      message: 'Not Authorised!',
      path: [operationName]
    }
  ]);
};

Then I have reusable spec helpers that are tied to specific rules. The two spec helper functions are tied to specific rules which are named isAdmin and isAuthenticated respectively.

export const adminAllowedSpec = (setupCallback, debug) => {
  const specFunc = debug ? it.only : it;
  return specFunc(`adminAllowedSpec: allows admin to view the current resource`, async () => {
    const { graphqlInstance, operationName, query, variables } = setupCallback();
    const admin = await setupAdmin({});
    await authorizeUser(admin, graphqlInstance);

    const res = await graphqlInstance.send({
      query,
      variables
    });

    validateSuccessfulRequest(res, operationName, debug);
  });
};

export const isNotAuthenticatedRejectedSpec = (setupCallback, debug) => {
  const specFunc = debug ? it.only : it;
  return specFunc(`isNotAuthenticatedRejectedSpec: should restrict if not authenticated`, async () => {
    const { graphqlInstance, operationName, query, variables, nullable } = setupCallback();
    unAuthorizeUser(graphqlInstance);
    const res = await graphqlInstance.send({
      query,
      variables
    });

    validateRejectedRequest(res, operationName, nullable, debug);
  });
};

And then I individually test resolvers independently from one another and I write a describe block related to specifically around permissions



describe('Mutation#myGraphqlResolverMutation', () => {
  // vars go here

  before(async () => {
    //...before setup    
  });

  beforeEach(async () => {
    //  this is just a request object that points to `/graphql`
    graphqlInstance = requestGraphqlInstance();

    await authorizeUser(testOwner, graphqlInstance);
    setupData = {
      graphqlInstance,
      operationName: 'myGraphqlResolverMutation',
      query: MY_GRAPHQL_RESOLVER_MUTATION,
      variables: {
        input: {
          myInputVariable: 5
        }
      }
    };
  });
  // there is a 'permissions' block in each of our resolvers forcing us to treat permissions differently than the resolvers logic
  describe('permissions', () => {
    adminAllowedSpec(() => setupData);
    providedUserAllowedToAccessObjectSpec(() => setupData, () => userWhoIsAllowedToAccess);
    isNotAuthenticatedRejectedSpec(() => setupData);
  });

  // the rest of the code the tests the resolver, nothing related to permissions
  it('does some cool mutations!', ()=>{

  })

});

So far this approach has been splendid. Right now, in the codebase I’m in, we have all the fields whiteliste in order to deny any permissions outright and we have to go field by field in order to think about the proper permissions and enable them. So we’ve gotten to the point where we think about what rules we need to implement, drop in the helper functions to test specific rules, fail them, and then get them passing once the rule is applied.

We’ve even gone as far as made a ruleSet helper functions since some specs are specific to the type of argument passed in. These help us group specific mutations/queries based off of the input and reuse all of the same specs for different resolvers.

fruitIdArgPermissionsSpec vegetableIdArgPermissionSpec and each of them import the smaller helper specs, so we can just drop in a function and get a lot of free tests.

Let me know if you guys need me to clean something up in this. Hope someone finds us around this topic. I’m considering even creating an article around the subject but I’d like to get more insight on the topic to see if anyone else has another approach. I’m still tweaking and thinking of new ways.

1reaction
quant-daddycommented, Jan 20, 2020

This seems to fail when the rule has { cache: ‘strict’ } option. I’m still looking for a good solution for this. Any ideas?

Read more comments on GitHub >

github_iconTop Results From Across the Web

The 5 unit testing guidelines - Medium
Unit tests have one assert per test. · Avoid if-statements in a unit test. · Unit tests only “new()” the unit under test....
Read more >
Unit testing rules | Pega Academy
Unit test rules to ensure application configurations behave as expected. After completing this module, you should be able to: Identify the role of...
Read more >
All about unit testing: 11 best practices and overview
7. One use case per unit test. Each test should focus on a single use case, and verify the output is as expected...
Read more >
SSW.Rules | Rules to Better Unit Tests
When you encounter a bug in your application you should never let the same bug happen again. The best way to do this...
Read more >
Unit Testing for Rules - Prometheus.io
You can use promtool to test your rules. # For a single test file. ./promtool test rules test.yml # If you have multiple...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found