How to check if node property is not None while iterating through materials?
An attribute can be None
The error NoneType has no attribute is a common error seen in blender scripting .  For example it can be the case that context.object is None
To the OP would recommend editing the title of question to reflect this error message more-so.

Error: AttributeError: 'NoneType' object has no attribute 'name'

thrown by node.node_tree.name == 'NodeGroupName'  is informing  node.node_tree has the value None.  Can test for this being the case, ie
if node.node_tree is  None:

as opposed to
if hasattr(node, &quot;node_tree&quot;):

Both will be true when the node tree is None
It is actually a strange case that the code in question would throw this error, as I'm not aware (yet) of how to add a group node and have no linked node_tree.  Speculate this is the result of deleting a node group from bpy.data.node_groups that was a node of a material node tree..
hence the last line of question script could be
if (node.type in [&quot;GROUP&quot;]  
        and node.node_tree 
        and node.node_tree.name == 'NodeGroupName'
        ):

since chaining ands ceases on first False
Using getattr
Since we pretty much would treat either the same using get attribute with the third argument default
node_tree = getattr(node, &quot;node_tree&quot;, None)

will be None in both cases.  IMO this &quot;trumps&quot; using both the has node tree then has name attribute test.  None has nothing, if we know it's none testing further for attributes would seem superfluous  Can also ensure that if  a node tree is not None it will have a name. bpy.types.ShaderNodeTree.name
Test script
Using the above with the &quot;double test&quot; for a node being of 'GROUP' type check it has a non none node tree.
Make a dictionary, with keys matching node group name containing a list of material name , node name tupples.
import bpy
from bpy import data
from collections import defaultdict

# all the group nodes in blendfile
print(data.node_groups.keys())
    
group_nodes = defaultdict(list)
# create a look up dictionary 
for m in data.materials:
    if m.use_nodes:
        for n in m.node_tree.nodes:
            nt = getattr(n, &quot;node_tree&quot;, None)
            if nt and n.type == 'GROUP':
                group_nodes[nt.name].append((m.name, n.name))
                
print(group_nodes) 

Output.
File with 1 node group &quot;Test_Material&quot;, 2 materials &quot;TestMat&quot; and &quot;TestMat.001&quot;.
The node group is used in both materials with name &quot;Group&quot; in both.
['Test_Material']

defaultdict(&lt;class 'list'&gt;, {'Test_Material': [('TestMat', 'Group'), ('TestMat.001', 'Group')]})

Notes:
Beware of equating to True or False in tests.  By default python  equates most types to True or false. Some examples of false are None, the integer 0 and an empty list []
Methods like hasattr return either True or False
if hasattr(foo, &quot;bar&quot;) == True:

if foo.bar  exists this reads somewhat akin to
if True == True:

instead simply use
if hasattr(foo, &quot;bar&quot;):

Changing above slightly to return boolean false instead of none when the node has no node_tree attribute, if nt: will be false in both cases, but we can discern between the two.
        nt = getattr(n, &quot;node_tree&quot;, False)
        if nt and n.type == 'GROUP':
            group_nodes[nt.name].append((m.name, n.name))
        if nt is None:
            print(f&quot;{m.name}:{n.name} node_tree is None&quot;)